├── .gitignore ├── README.md ├── env.example ├── gptsql ├── __init__.py ├── __main__.py ├── func_tools.py └── utils.py ├── poetry.lock └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .env* 2 | venv 3 | __pycache__ 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gptsql 2 | 3 | An LLM-powered chat interface to your database. This tool understands Postgres syntax and can easily translate English queries into proper SQL queries. Because of the wide training of the LLM model it can also infer relevant information about the structure and meaning of your tables and data. Uses an [Open AI](https://openai.com) model via the [Assistant API](https://platform.openai.com/docs/assistants/overview). 4 | 5 | There are lots of tools around to enable "chat with your data" (mostly based on the [RAG](https://research.ibm.com/blog/retrieval-augmented-generation-RAG) architecture), but this is actually the quickest way to enable LLM chat with your data - no preparation is needed. 6 | 7 | 8 | Here's a quick demo showing natural language queries about the [IMDB](https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/2QYZBT) movie database. You can even see the Assistant encounter SQL errors and then correct itself: 9 | 10 | 11 | 12 | https://github.com/tatari-tv/gptsql/assets/80947/7e946bdd-1ed9-4a03-9dc1-aca8d3a44926 13 | 14 | 15 | 16 | 17 | ## Installation 18 | 19 | You will need: 20 | 21 | 1. credentials for your database 22 | 2. an OpenAI [API Key](https://platform.openai.com/account/api-keys) from your OpenAI account. 23 | 24 | then 25 | 26 | ``` 27 | pip install gptsql 28 | ``` 29 | 30 | or download the source. 31 | 32 | Run the CLI with: 33 | 34 | gptsql 35 | 36 | or use `python -m gptsql` to run from source. 37 | 38 | ## What can it do? 39 | 40 | Then Open AI model understands most Postgres syntax, so it can generate both generic SQL commands as well as very Postgres-specific ones like querying system settings. 41 | 42 | The LLM is also good at analyzing tables, understanding what they are likely used for, and inferring relationships between tables. It is quite good at writing JOINs between tables without explicit instruction. 43 | 44 | It can write queries to group and summarize results. 45 | 46 | By virtue of the Assistant API, it can also write multi-step queries and combine the results of queries together. It is also maintaining a history of the chat, so you can easily ask follow up questions like "now summarize by month" that refer back to prior queries. 47 | 48 | At the moment the tool limits to only SELECT queries and does not allow mutations (but see the [SAFETY](#safety) section for details). 49 | 50 | I wrote a more detailed background about the project on my blog: https://scottpersinger.com/2023/12/07/build-an-openai-assistant-to-chat-with-your-postgres-database/. 51 | 52 | ## Chat examples 53 | 54 | ``` 55 | % gptsql 56 | 57 | Welcome to GPTSQL, the chat interface to your Postgres database. 58 | 59 | > show me the available schemas 60 | ⠋ thinking... Running select query: SELECT schema_name FROM information_schema.schemata ORDER BY schema_name; 61 | [assistant] --> The available schemas in the database are: 62 | 63 | 1. information_schema 64 | 2. pg_catalog 65 | 3. pg_toast 66 | 4. public 67 | ``` 68 | 69 | Show tables matching a string: 70 | ``` 71 | > show all tables that have "sales" in the name 72 | ⠴ thinking... Running select query: SELECT table_name FROM information_schema.tables WHERE table_name LIKE '%%sales%%' ORDER BY table_name; 73 | [assistant] --> The tables with 'sales' in the name are as follows: 74 | 75 | - salesorderdetail 76 | - salesorderheader 77 | - salesorderheadersalesreason 78 | - salesperson 79 | - salespersonquotahistory 80 | - salesreason 81 | - salestaxrate 82 | - salesterritory 83 | - salesterritoryhistory 84 | - vsalesperson 85 | - vsalespersonsalesbyfiscalyears 86 | - vsalespersonsalesbyfiscalyearsdata 87 | ``` 88 | List columns for a table: 89 | ``` 90 | > show the columns of the salesorderheader table 91 | ⠹ thinking... Running select query: SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'salesorderheader' ORDER BY ordinal_position; 92 | [assistant] --> The columns of the `salesorderheader` table along with their data types are as follows: 93 | 94 | - `salesorderid` (integer) 95 | - `revisionnumber` (smallint) 96 | - `orderdate` (timestamp without time zone) 97 | - `duedate` (timestamp without time zone) 98 | - `shipdate` (timestamp without time zone) 99 | - `status` (smallint) 100 | - `subtotal` (numeric) 101 | - `taxamt` (numeric) 102 | - `freight` (numeric) 103 | - `totaldue` (numeric) 104 | - `comment` (character varying) 105 | - `rowguid` (uuid) 106 | - `modifieddate` (timestamp without time zone) 107 | ``` 108 | Run a calculation against a table: 109 | ``` 110 | > calculate the sum of the totaldue column by month 111 | ⠇ thinking... Running select query: SELECT DATE_TRUNC('month', orderdate) AS month, SUM(totaldue) AS total_sales FROM sales.salesorderheader GROUP BY month ORDER BY month; 112 | [assistant] --> Here are the sums of the `totaldue` column by month: 113 | 114 | Month Total Sales 115 | ------------------------------------- 116 | 2011-05-01 567,020.95 117 | 2011-06-01 507,096.47 118 | 2011-07-01 2,292,182.88 119 | 2011-08-01 2,800,576.17 120 | 2011-09-01 554,791.61 121 | 2014-02-01 1,478,213.29 122 | 2014-03-01 8,097,036.31 123 | 2014-04-01 1,985,886.15 124 | 2014-05-01 6,006,183.21 125 | 2014-06-01 54,151.48 126 | These figures represent the total sales for each month. 127 | ``` 128 | Query data from a table: 129 | ``` 130 | > show me the names of the stores 131 | ⠹ thinking... Running select query: SELECT name FROM sales.store; 132 | > print the results 133 | ⠧ thinking...LAST QUERY RESULTS: 134 | ⠇ thinking... name 135 | 0 Next-Door Bike Store 136 | 1 Professional Sales and Service 137 | 2 Riders Company 138 | 3 The Bike Mechanics 139 | 4 Nationwide Supply 140 | 5 Area Bike Accessories 141 | 6 Bicycle Accessories and Kits 142 | 7 Clamps & Brackets Co. 143 | 8 Valley Bicycle Specialists 144 | 9 New Bikes Company 145 | 10 Vinyl and Plastic Goods Corporation 146 | 11 Top of the Line Bikes 147 | 12 Fun Toys and Bikes 148 | 13 Great Bikes 149 | 14 Metropolitan Sales and Rental 150 | 15 Irregulars Outlet 151 | 16 Valley Toy Store 152 | 17 Worthwhile Activity Store 153 | ``` 154 | Explain your data: 155 | ``` 156 | > what is the purpose of the salespersonquotahistory table? 157 | [assistant] --> The purpose of the `salespersonquotahistory` table is typically to maintain a record of the sales quota assigned to each salesperson over time. The table likely includes columns for: 158 | 159 | - Identifying the salesperson, which may reference a `businessentityid` or similar key that links to another table containing salesperson details. 160 | - The sales quota amount, which indicates the target sales figure the salesperson is expected to meet for a given period. 161 | - Date fields, which may specify the period or specific dates for the quota. 162 | - Any changes or historical adjustments made to the quota over time. 163 | 164 | It serves as a historical log that can be used for performance tracking, compensation calculation, sales management, and analysis. The exact columns and usage may vary depending on the specific design of the database. If you have access to the table structure, you can review the columns to understand more about the data it holds. Would you like me to retrieve the columns of the `salespersonquotahistory` table to provide more detailed information? 165 | ``` 166 | Easily run custom Postgres SQL: 167 | ``` 168 | > show the top 10 tables by size on disk 169 | ⠸ thinking... Running select query: SELECT table_schema || '.' || table_name AS full_table_name, 170 | pg_total_relation_size('"' || table_schema || '"."' || table_name || '"') AS size_on_disk 171 | FROM information_schema.tables 172 | WHERE table_schema NOT IN ('pg_catalog', 'information_schema') 173 | ORDER BY size_on_disk DESC 174 | LIMIT 10; 175 | [assistant] --> The top 10 tables by size on disk are as follows: 176 | 177 | 1. `sales.salesorderdetail`: 15,073,280 bytes 178 | 2. `person.person`: 14,688,256 bytes 179 | 3. `production.transactionhistory`: 11,304,960 bytes 180 | 4. `production.workorderrouting`: 9,355,264 bytes 181 | 5. `production.transactionhistoryarchive`: 8,896,512 bytes 182 | 6. `production.workorder`: 7,823,360 bytes 183 | 7. `sales.salesorderheader`: 6,676,480 bytes 184 | 8. `production.productphoto`: 4,431,872 bytes 185 | 9. `person.address`: 3,522,560 bytes 186 | 10. `person.password`: 2,842,624 bytes 187 | 188 | These sizes are in bytes, and the tables are listed in decreasing order of their size on disk. 189 | ``` 190 | 191 | ### Configuration 192 | 193 | You can configure the database connection either using `psql` style command line arguments 194 | or the env vars `DBHOST`, `DBNAME`, `DBUSER`, `DBPASSWORD`, `DBPORT`. 195 | 196 | Else when you first run the program it will prompt you for the connection credentials as 197 | well as your OpenAI API key. 198 | 199 | After first setup all the configuration information is stored in `~/.gptsql`. Delete that 200 | file if you want to start over. 201 | 202 | ## How it works 203 | 204 | `gptsql` uses the OpenAI [Assistants API](https://platform.openai.com/docs/assistants/overview) to create an intelligent assistant to work with your database. 205 | The key to accessing the database is providing a function _tool_ to the assistant. Amazingly 206 | only a single function is required: 207 | 208 | ```json 209 | { 210 | "type": "function", 211 | "function": { 212 | "name": "run_sql_command", 213 | "description": "Execute any SQL command against the Postgres datbase", 214 | } 215 | } 216 | ``` 217 | 218 | When requested the LLM automatically generates the right SQL and calls this function to execute it. The query results are then returned to the Assistant where it can decide to print or summarize the results. 219 | 220 | If the LLM needs to know about your tables it will just execute SQL commands against the 221 | `information schema` to extract it. 222 | 223 | Since table reference is so common, we help the assistant by pre-emptively injecting the table 224 | list into the LLM prompt. 225 | 226 | Because of the LLM context limits, you won't always be able to see all the rows returned from a query. 227 | So we provide a second function tool, `show_query_results` which can print up to 200 rows resulting from 228 | the last query. Sometimes the assistant is smart enough to call this function by itself, but other times 229 | you may have to request "print results" to see all the result rows. 230 | 231 | ### Command Reference 232 | 233 | There are a few system commands supported for meta operations: 234 | 235 | `help` - show system commands 236 | 237 | `connection` - show the current db connection details, and the active LLM model 238 | 239 | `history` - print the assistant's message history 240 | 241 | `new thread` - start a new thread (clearing out any existing conversation context) 242 | 243 | `exit` or ctrl-d to exit 244 | 245 | If you want to change the LLM model you can edit the assistant via the OpenAI web portal. 246 | 247 | ## SAFETY 248 | 249 | **Please do not run this against a production database!** And **make sure you have a backup** of your data. That said, the query function has a simple protector which will refuse to run any query that doesn't start with `SELECT`. Note that this is not foolproof. It is very likely that the LLM can construct a destructive query which will get around this simple check, if you ask it properly. So don't rely on this for perfect safety. I strongly recommend running with a `read-only` db connection just in case. 250 | 251 | Also please note that this tool **sends your data to OpenAI**. Query results are sent back to Assistant API for processing. Please make your own decision on whether you are OK with this or not. 252 | 253 | ## Limitations 254 | 255 | The biggest limitation is that the LLM is **slooooowwww**. In my testing it can easily take 20-30 seconds of "thinking" before the LLM responds. One reason is that the LLM runs once when you issue your question, and then it runs again to process any results returned from the functions. 256 | 257 | It is also the case the the Assistants API will run multiple "steps" to process your question, even though we don't get a lot of feedback when this is happening. 258 | 259 | One other thing: the tool is **expensive** :). I ran up a bill of about $100 just doing development. It is recommnded to stick with the **GPT3** model if you want to keep your costs down. 260 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | # Copy to `.env`. 2 | # You can source with: export $(cat .env) 3 | export DBHOST= 4 | export DBNAME= 5 | export DBUSER= 6 | export DBPASSWORD= 7 | export OPENAI_API_KEY= 8 | 9 | -------------------------------------------------------------------------------- /gptsql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatari-tv/gptsql/2240b2e3d1a846d54f461b3cdb9e0867e26057e9/gptsql/__init__.py -------------------------------------------------------------------------------- /gptsql/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from datetime import datetime 3 | import importlib.metadata 4 | import json 5 | import os 6 | import psycopg2 7 | import time 8 | import toml 9 | 10 | from openai import OpenAI 11 | import openai 12 | from prompt_toolkit import PromptSession, prompt 13 | from prompt_toolkit.history import FileHistory 14 | from halo import Halo 15 | from sqlalchemy import create_engine 16 | 17 | from .func_tools import call_my_function, get_table_list 18 | 19 | ASSISTANT_NAME="GPTSQL" 20 | GPT_MODEL3="gpt-3.5-turbo-1106" 21 | GPT_MODEL4="gpt-4-1106-preview" 22 | GPT_MODEL=GPT_MODEL4 23 | #GPT_MODEL="gpt-4-1106-preview" 24 | 25 | # Replace these with your specific database credentials 26 | 27 | class GPTSql: 28 | FUNCTION_TOOLS = [ 29 | { 30 | "type": "function", 31 | "function": { 32 | "name": "run_sql_command", 33 | "description": "Execute any SQL command against the Postgres datbase", 34 | "parameters": { 35 | "type": "object", 36 | "properties": { 37 | "query": { 38 | "type":"string", 39 | "description":"Postgres syntax SQL query" 40 | } 41 | } 42 | } 43 | } 44 | }, 45 | { 46 | "type": "function", 47 | "function": { 48 | "name": "show_long_query_results_on_demand", 49 | "description": "Only call this function if the user requests to 'print all results'", 50 | "parameters": { 51 | "type": "object", 52 | "properties": {}, 53 | "required": [], 54 | } 55 | } 56 | }, 57 | ] 58 | CONFIG_FILE = os.path.expanduser('~/.gptsql') 59 | 60 | def __init__(self) -> None: 61 | self.load_config() 62 | 63 | args = self.parse_args() 64 | 65 | if 'DBUSER' in self.config and 'DBHOST' in self.config: 66 | db_username = self.config['DBUSER'] 67 | db_password = self.config['DBPASSWORD'] 68 | db_host = self.config['DBHOST'] 69 | db_port = int(self.config['DBPORT']) 70 | db_name = self.config['DBNAME'] 71 | else: 72 | db_username = args.username or os.environ.get('DBUSER') 73 | db_password = args.password or os.environ.get('DBPASSWORD') 74 | db_host = args.host or os.environ.get('DBHOST') 75 | db_port = args.port or 5432 76 | db_name = args.dbname or os.environ.get('DBNAME') 77 | 78 | if db_host is None: 79 | connection_good = False 80 | while not connection_good: 81 | print("Let's setup your database connection...") 82 | db_host = prompt("Enter your database host: ") 83 | db_username = prompt("Enter your database username: ") 84 | db_password = prompt("Enter your database password: ", is_password=True) 85 | db_name = prompt("Enter the database name: ") 86 | db_port = prompt("Enter your database port (5432): ") or 5432 87 | db_port = int(db_port) 88 | print("Validating connection info...") 89 | try: 90 | pgconn = psycopg2.connect( 91 | f"host={db_host} dbname={db_name} user={db_username} password={db_password}", 92 | connect_timeout=10 93 | ) 94 | with pgconn.cursor() as cursor: 95 | cursor.execute("SELECT version();") 96 | connection_good = True 97 | except psycopg2.OperationalError as e: 98 | print("Error: ", e) 99 | continue 100 | self.config |= { 101 | "DBUSER": db_username, 102 | "DBPASSWORD": db_password, 103 | "DBHOST": db_host, 104 | "DBPORT": db_port, 105 | "DBNAME": db_name 106 | } 107 | 108 | self.save_config() 109 | 110 | # PostgreSQL connection string format 111 | self.db_config = { 112 | 'db_username': db_username, 113 | 'db_password': db_password, 114 | 'db_host': db_host, 115 | 'db_port': db_port, 116 | 'db_name': db_name 117 | } 118 | self.connection_string = f'postgresql://{db_username}:{db_password}@{db_host}:{db_port}/{db_name}' 119 | 120 | self.engine = create_engine(self.connection_string) 121 | # Connect to your database 122 | self.pgconn = psycopg2.connect( 123 | f"host={db_host} dbname={db_name} user={db_username} password={db_password}" 124 | ) 125 | self.thread = None 126 | 127 | api_key = self.config.get('OPENAI_API_KEY') or os.environ.get('OPENAI_API_KEY') 128 | if api_key is None: 129 | api_key = prompt("Enter your Open AI API key: ", is_password=True) 130 | self.save_config("OPENAI_API_KEY", api_key) 131 | 132 | if 'model' not in self.config: 133 | print("Which model do you want to use?") 134 | print(f"1. {GPT_MODEL3}") 135 | print(f"2. {GPT_MODEL4}") 136 | choice = prompt("(1 or 2) >") 137 | if choice == "1": 138 | self.save_config("model", GPT_MODEL3) 139 | else: 140 | self.save_config("model", GPT_MODEL4) 141 | 142 | self.oaclient = OpenAI(api_key=api_key) 143 | self.get_or_create_assistant() 144 | 145 | def parse_args(self): 146 | parser = argparse.ArgumentParser(add_help=False) 147 | parser.add_argument('-help', '--help', action='help', default=argparse.SUPPRESS, help='Show this help message and exit') 148 | 149 | parser.add_argument('-h', '--host', type=str, required=False) 150 | parser.add_argument('-p', '--port', type=int, required=False) 151 | parser.add_argument('-U', '--username', type=str, required=False) 152 | parser.add_argument('-d', '--dbname', type=str, required=False) 153 | parser.add_argument('--password', type=str, required=False) 154 | 155 | return parser.parse_args() 156 | 157 | def save_config(self, key=None, value=None): 158 | if key and value: 159 | self.config[key] = value 160 | 161 | for k, v in self.config.items(): 162 | if isinstance(v, datetime): 163 | self.config[k] = v.isoformat() 164 | 165 | with open(self.CONFIG_FILE, 'w') as f: 166 | f.write(json.dumps(self.config)) 167 | 168 | def load_config(self): 169 | self.config = {} 170 | if os.path.exists(self.CONFIG_FILE): 171 | with open(self.CONFIG_FILE, 'r') as f: 172 | self.config = json.loads(f.read()) 173 | 174 | for k, v in self.config.items(): 175 | try: 176 | dt = datetime.fromisoformat(v) 177 | self.config[k] = dt 178 | except: 179 | pass 180 | 181 | def get_version(self): 182 | try: 183 | pyproject = toml.load(os.path.join(os.path.dirname(__file__), "..", "pyproject.toml")) 184 | return pyproject["tool"]["poetry"]["version"] 185 | except: 186 | return importlib.metadata.version("gptsql") 187 | 188 | def get_or_create_assistant(self): 189 | # Create or retriveve our Assistant. We also upload the schema file 190 | # for RAG uses by the assistant. 191 | self.assistant = None 192 | if self.config.get("assistant_id") is not None: 193 | try: 194 | self.assistant = self.oaclient.beta.assistants.retrieve(self.config["assistant_id"]) 195 | except openai.NotFoundError: 196 | pass 197 | 198 | if self.assistant is None: 199 | print("Creating your PSQL assistant") 200 | self.assistant = self.oaclient.beta.assistants.create( 201 | name=ASSISTANT_NAME, 202 | instructions=""" 203 | You are an assistant helping with data analysis and to query a postgres database. 204 | """, 205 | tools=[{"type": "code_interpreter"},{"type": "retrieval"}] + self.FUNCTION_TOOLS, 206 | model=self.config['model'] 207 | ) 208 | self.save_config("assistant_id", self.assistant.id) 209 | 210 | def chat_loop(self): 211 | session = PromptSession(history=FileHistory(os.path.expanduser('~/.myhistory'))) 212 | 213 | if self.config.get("thread_id") is not None: 214 | thread = self.oaclient.beta.threads.retrieve(self.config["thread_id"]) 215 | else: 216 | thread = self.oaclient.beta.threads.create() 217 | self.save_config("thread_id", thread.id) 218 | 219 | self.thread = thread 220 | 221 | if self.config.get("last_run_id") is not None: 222 | try: 223 | self.oaclient.beta.threads.runs.cancel(thread_id=thread.id, run_id=self.config["last_run_id"]) 224 | except(openai.BadRequestError, openai.NotFoundError): 225 | pass 226 | 227 | self.last_message_created_at = self.config.get('last_messsage_time') 228 | self.table_list = get_table_list(self.engine) 229 | 230 | spinner = Halo(text='thinking', spinner='dots') 231 | self.spinner = spinner 232 | 233 | print(""" 234 | Welcome to GPTSQL, the chat interface to your Postgres database. 235 | You can ask questions like: 236 | "help" (show some system commands) 237 | "show all the tables" 238 | "show me the first 10 rows of the users table" 239 | "show me the schema for the orders table" 240 | """) 241 | while True: 242 | try: 243 | cmd = session.prompt("\n> ") 244 | if cmd == "": 245 | continue 246 | elif cmd == "history": 247 | self.display_messages(show_all=True) 248 | continue 249 | elif cmd == "help": 250 | print(""" 251 | connection - show the database connection info 252 | history - show the complete message history 253 | new thread - start a new thread 254 | exit 255 | """) 256 | continue 257 | elif cmd == "new thread": 258 | if session.prompt("Do you want to start a new thread (y/n)? ") == "y": 259 | thread = self.oaclient.beta.threads.create() 260 | self.save_config("thread_id", thread.id) 261 | self.thread = thread 262 | continue 263 | elif cmd == "connection": 264 | print(f"Host: {self.db_config['db_host']}, Database: {self.db_config['db_name']}, User: {self.db_config['db_username']}") 265 | print(f"Model: {self.assistant.model}") 266 | print(f"Version: {self.get_version()}") 267 | continue 268 | elif cmd == "exit": 269 | return 270 | 271 | cmd = "These are the tables in the database:\n" + ",".join(self.table_list) + "\n----\n" + cmd 272 | spinner.start("thinking...") 273 | self.process_command(thread, cmd) 274 | spinner.stop() 275 | self.display_messages() 276 | except (KeyboardInterrupt, EOFError): 277 | spinner.stop() 278 | return 279 | 280 | def display_messages(self, show_all=False): 281 | messages = self.oaclient.beta.threads.messages.list( 282 | thread_id=self.thread.id 283 | ) 284 | for msg in reversed(list(messages)): 285 | if msg.role == "user" and not show_all: 286 | continue 287 | if self.last_message_created_at is None or (msg.created_at > self.last_message_created_at) or show_all: 288 | self.last_message_created_at = msg.created_at 289 | self.save_config("last_messsage_time", self.last_message_created_at) 290 | if hasattr(msg.content[0], 'text'): 291 | print(f"[{msg.role}] --> {msg.content[0].text.value}") 292 | else: 293 | print(f"[{msg.role}] --> {type(msg)}") 294 | 295 | def log(self, msg): 296 | self.spinner.start(msg); 297 | #print(msg) 298 | 299 | def process_command(self, thread, cmd: str): 300 | self.oaclient.beta.threads.messages.create( 301 | thread_id=thread.id, 302 | role="user", 303 | content=cmd 304 | ) 305 | runobj = self.oaclient.beta.threads.runs.create( 306 | thread_id=thread.id, 307 | assistant_id=self.assistant.id 308 | ) 309 | self.save_config("last_run_id", runobj.id) 310 | last_step_count = 0 311 | while runobj.status not in ["completed", "expired", "cancelled", "failed"]: 312 | if runobj.status == "in_progress": 313 | # check for new steps 314 | run_steps = self.oaclient.beta.threads.runs.steps.list( 315 | thread_id=thread.id, 316 | run_id=runobj.id 317 | ) 318 | run_steps = list(run_steps) 319 | #print(run_steps) 320 | #print("\n\n") 321 | if len(run_steps) > last_step_count: 322 | for step in run_steps[last_step_count:]: 323 | for step_detail in step.step_details: 324 | if step_detail[0] == 'tool_calls': 325 | for tool_call in step_detail[1]: 326 | #if 'Function' in str(type(tool_call)): 327 | # self.log(f" --> {tool_call.function.name}()") 328 | if 'Code' in str(type(tool_call)): 329 | self.log(f" [code] {tool_call.code_interpreter.input}") 330 | last_step_count = len(run_steps) 331 | elif runobj.status == "requires_action": 332 | # Run any functions that the assistant has requested 333 | if runobj.required_action.type == "submit_tool_outputs": 334 | tool_outputs = [] 335 | for tool_call in runobj.required_action.submit_tool_outputs.tool_calls: 336 | self.log(f" --> {tool_call.function.name}()") 337 | res = str(call_my_function(self.engine, tool_call.function.name, json.loads(tool_call.function.arguments))) 338 | tool_outputs.append({ 339 | "tool_call_id": tool_call.id, 340 | "output": res 341 | }) 342 | self.oaclient.beta.threads.runs.submit_tool_outputs( 343 | thread_id=thread.id, 344 | run_id=runobj.id, 345 | tool_outputs=tool_outputs 346 | ) 347 | self.spinner.text = "considering results..." 348 | else: 349 | print("Unknown action: ", runobj.required_action.type) 350 | time.sleep(1) 351 | runobj = self.oaclient.beta.threads.runs.retrieve(thread_id=thread.id, run_id=runobj.id) 352 | 353 | 354 | def main(): 355 | gptsql = GPTSql() 356 | gptsql.chat_loop() 357 | 358 | if __name__ == "__main__": 359 | main() 360 | 361 | -------------------------------------------------------------------------------- /gptsql/func_tools.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | from tabulate import tabulate 3 | import pandas as pd 4 | from sqlalchemy import text 5 | 6 | last_results: pd.DataFrame = None 7 | 8 | def call_my_function(engine, name, fargs = {}): 9 | global last_results 10 | 11 | if name == "run_sql_command": 12 | query = fargs.get("query") 13 | if query and query.lower().startswith("select"): 14 | query = query.replace('%', '%%') 15 | print(colored(query, "blue")) 16 | try: 17 | # convert DataFrame to json 18 | last_results = pd.read_sql_query(query, engine) 19 | if last_results.shape[0] > 20: 20 | return last_results.head(20).to_json() 21 | else: 22 | return last_results.to_json() 23 | except Exception as e: 24 | print(colored(f"Database query failed: {e}", "red")) 25 | return {"error", f" Database query failed: {e}"} 26 | else: 27 | print("Invalid query: ", query) 28 | return {"error": f"Failed to run non-select query '{query}'"} 29 | elif name == "show_long_query_results_on_demand": 30 | if last_results is None: 31 | print("No results to show") 32 | else: 33 | print("LAST QUERY RESULTS:") 34 | with pd.option_context('display.max_rows', 200, 'display.min_rows', 200): 35 | print(last_results) 36 | return "OK" 37 | 38 | def get_table_list(engine, schema = "public"): 39 | sql = f""" 40 | SELECT table_name from INFORMATION_SCHEMA.tables where table_schema = '{schema}' ORDER BY table_name 41 | """ 42 | with engine.connect() as connection: 43 | rows = list(connection.execute(text(sql))) 44 | return [r[0] for r in rows] 45 | -------------------------------------------------------------------------------- /gptsql/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | from sqlalchemy import text 4 | from tabulate import tabulate 5 | 6 | # Currently none of these functions are used. 7 | 8 | def more_functions(engine, name, fargs): 9 | if name == "list_schemas": 10 | with engine.connect() as connection: 11 | rows = connection.execute(text("SELECT schema_name FROM information_schema.schemata")) 12 | return [row[0] for row in rows] 13 | 14 | elif name == "list_tables": 15 | schema = fargs.get("schema", "public") 16 | sql = f""" 17 | SELECT table_schema, table_name 18 | --, pg_size_pretty(total_bytes) AS total, to_char(row_estimate, 'FM999,999,999,999') as rows 19 | FROM ( 20 | SELECT *, total_bytes-index_bytes-coalesce(toast_bytes,0) AS table_bytes FROM ( 21 | SELECT c.oid,nspname AS table_schema, relname AS table_name 22 | , c.reltuples AS row_estimate 23 | , pg_total_relation_size(c.oid) AS total_bytes 24 | , pg_indexes_size(c.oid) AS index_bytes 25 | , pg_total_relation_size(reltoastrelid) AS toast_bytes 26 | FROM pg_class c 27 | LEFT JOIN pg_namespace n ON n.oid = c.relnamespace 28 | WHERE relkind = 'r' and nspname='{schema}' 29 | ) a order by table_bytes desc 30 | ) a; 31 | """ 32 | with engine.connect() as connection: 33 | rows = connection.execute(text(sql)) 34 | cols = ['schema', 'table'] #, 'size', 'rows' 35 | return(tabulate(list(rows), headers=[cols])) 36 | elif name == "get_table_schema": 37 | schema = fargs.get("schema", "public") 38 | table = fargs.get("table") 39 | sql = f""" 40 | SELECT 41 | column_name, 42 | data_type 43 | FROM 44 | information_schema.columns 45 | WHERE 46 | table_schema = '{schema}' and table_name='{table}'; 47 | """ 48 | with engine.connect() as connection: 49 | rows = connection.execute(text(sql)) 50 | cols = ['column', 'type'] 51 | return(tabulate(list(rows), headers=[cols])) 52 | 53 | # This was an attempt to collect the database schema and make it available 54 | # via RAG to the LLM. But none of my experiments with that worked very well. 55 | 56 | def download_database_schema(pg_connection): 57 | if not os.path.exists('./schema.md'): 58 | with pg_connection.cursor() as cursor: 59 | # Use copy_from to copy data from a file to a table 60 | sql = """ 61 | COPY (select table_schema, table_name, column_name, data_type 62 | from information_schema.columns where table_schema not in ('information_schema','pg_catalog') 63 | order by table_schema, table_name, column_name) TO STDOUT WITH CSV HEADER 64 | """ 65 | with open('./schema.csv', 'w') as f: 66 | cursor.copy_expert(sql, f) 67 | # Read the csv file line by line using a CSV reader 68 | # and write the data out to a markdown file 69 | with open('./schema.csv', 'r') as f: 70 | with open('./schema.md', 'w') as md: 71 | reader = csv.reader(f) 72 | wrote_header = False 73 | for row in reader: 74 | if not wrote_header: 75 | wrote_header = True 76 | md.write("# Database Schema\n\n") 77 | md.write("|" + ("|".join(row)) +"|\n") 78 | md.write("|" + ("|".join(["------" for col in row])) +"|\n") 79 | continue 80 | md.write("|" + ("|".join(row)) + "|\n") 81 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.6.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.8" 10 | files = [ 11 | {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, 12 | {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, 13 | ] 14 | 15 | [[package]] 16 | name = "anyio" 17 | version = "3.7.1" 18 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.7" 22 | files = [ 23 | {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, 24 | {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, 25 | ] 26 | 27 | [package.dependencies] 28 | exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} 29 | idna = ">=2.8" 30 | sniffio = ">=1.1" 31 | 32 | [package.extras] 33 | doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] 34 | test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] 35 | trio = ["trio (<0.22)"] 36 | 37 | [[package]] 38 | name = "certifi" 39 | version = "2023.11.17" 40 | description = "Python package for providing Mozilla's CA Bundle." 41 | category = "main" 42 | optional = false 43 | python-versions = ">=3.6" 44 | files = [ 45 | {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, 46 | {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, 47 | ] 48 | 49 | [[package]] 50 | name = "colorama" 51 | version = "0.4.6" 52 | description = "Cross-platform colored terminal text." 53 | category = "main" 54 | optional = false 55 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 56 | files = [ 57 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 58 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 59 | ] 60 | 61 | [[package]] 62 | name = "distro" 63 | version = "1.8.0" 64 | description = "Distro - an OS platform information API" 65 | category = "main" 66 | optional = false 67 | python-versions = ">=3.6" 68 | files = [ 69 | {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"}, 70 | {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, 71 | ] 72 | 73 | [[package]] 74 | name = "exceptiongroup" 75 | version = "1.2.0" 76 | description = "Backport of PEP 654 (exception groups)" 77 | category = "main" 78 | optional = false 79 | python-versions = ">=3.7" 80 | files = [ 81 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 82 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 83 | ] 84 | 85 | [package.extras] 86 | test = ["pytest (>=6)"] 87 | 88 | [[package]] 89 | name = "greenlet" 90 | version = "3.0.1" 91 | description = "Lightweight in-process concurrent programming" 92 | category = "main" 93 | optional = false 94 | python-versions = ">=3.7" 95 | files = [ 96 | {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, 97 | {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, 98 | {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, 99 | {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, 100 | {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, 101 | {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, 102 | {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, 103 | {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, 104 | {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, 105 | {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, 106 | {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, 107 | {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, 108 | {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, 109 | {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, 110 | {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, 111 | {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, 112 | {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, 113 | {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, 114 | {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, 115 | {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, 116 | {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, 117 | {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, 118 | {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, 119 | {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, 120 | {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, 121 | {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, 122 | {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, 123 | {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, 124 | {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, 125 | {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, 126 | {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, 127 | {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, 128 | {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, 129 | {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, 130 | {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, 131 | {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, 132 | {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, 133 | {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, 134 | {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, 135 | {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, 136 | {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, 137 | {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, 138 | {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, 139 | {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, 140 | {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, 141 | {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, 142 | {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, 143 | {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, 144 | {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, 145 | {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, 146 | {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, 147 | {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, 148 | {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, 149 | {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, 150 | {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, 151 | {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, 152 | {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, 153 | ] 154 | 155 | [package.extras] 156 | docs = ["Sphinx"] 157 | test = ["objgraph", "psutil"] 158 | 159 | [[package]] 160 | name = "h11" 161 | version = "0.14.0" 162 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 163 | category = "main" 164 | optional = false 165 | python-versions = ">=3.7" 166 | files = [ 167 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 168 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 169 | ] 170 | 171 | [[package]] 172 | name = "halo" 173 | version = "0.0.31" 174 | description = "Beautiful terminal spinners in Python" 175 | category = "main" 176 | optional = false 177 | python-versions = ">=3.4" 178 | files = [ 179 | {file = "halo-0.0.31-py2-none-any.whl", hash = "sha256:5350488fb7d2aa7c31a1344120cee67a872901ce8858f60da7946cef96c208ab"}, 180 | {file = "halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6"}, 181 | ] 182 | 183 | [package.dependencies] 184 | colorama = ">=0.3.9" 185 | log-symbols = ">=0.0.14" 186 | six = ">=1.12.0" 187 | spinners = ">=0.0.24" 188 | termcolor = ">=1.1.0" 189 | 190 | [package.extras] 191 | ipython = ["IPython (==5.7.0)", "ipywidgets (==7.1.0)"] 192 | 193 | [[package]] 194 | name = "httpcore" 195 | version = "1.0.2" 196 | description = "A minimal low-level HTTP client." 197 | category = "main" 198 | optional = false 199 | python-versions = ">=3.8" 200 | files = [ 201 | {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, 202 | {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, 203 | ] 204 | 205 | [package.dependencies] 206 | certifi = "*" 207 | h11 = ">=0.13,<0.15" 208 | 209 | [package.extras] 210 | asyncio = ["anyio (>=4.0,<5.0)"] 211 | http2 = ["h2 (>=3,<5)"] 212 | socks = ["socksio (>=1.0.0,<2.0.0)"] 213 | trio = ["trio (>=0.22.0,<0.23.0)"] 214 | 215 | [[package]] 216 | name = "httpx" 217 | version = "0.25.2" 218 | description = "The next generation HTTP client." 219 | category = "main" 220 | optional = false 221 | python-versions = ">=3.8" 222 | files = [ 223 | {file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"}, 224 | {file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"}, 225 | ] 226 | 227 | [package.dependencies] 228 | anyio = "*" 229 | certifi = "*" 230 | httpcore = ">=1.0.0,<2.0.0" 231 | idna = "*" 232 | sniffio = "*" 233 | 234 | [package.extras] 235 | brotli = ["brotli", "brotlicffi"] 236 | cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] 237 | http2 = ["h2 (>=3,<5)"] 238 | socks = ["socksio (>=1.0.0,<2.0.0)"] 239 | 240 | [[package]] 241 | name = "idna" 242 | version = "3.6" 243 | description = "Internationalized Domain Names in Applications (IDNA)" 244 | category = "main" 245 | optional = false 246 | python-versions = ">=3.5" 247 | files = [ 248 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 249 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 250 | ] 251 | 252 | [[package]] 253 | name = "log-symbols" 254 | version = "0.0.14" 255 | description = "Colored symbols for various log levels for Python" 256 | category = "main" 257 | optional = false 258 | python-versions = "*" 259 | files = [ 260 | {file = "log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca"}, 261 | {file = "log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556"}, 262 | ] 263 | 264 | [package.dependencies] 265 | colorama = ">=0.3.9" 266 | 267 | [[package]] 268 | name = "numpy" 269 | version = "1.26.2" 270 | description = "Fundamental package for array computing in Python" 271 | category = "main" 272 | optional = false 273 | python-versions = ">=3.9" 274 | files = [ 275 | {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, 276 | {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, 277 | {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, 278 | {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, 279 | {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, 280 | {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, 281 | {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, 282 | {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, 283 | {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, 284 | {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, 285 | {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, 286 | {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, 287 | {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, 288 | {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, 289 | {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, 290 | {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, 291 | {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, 292 | {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, 293 | {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, 294 | {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, 295 | {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, 296 | {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, 297 | {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, 298 | {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, 299 | {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, 300 | {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, 301 | {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, 302 | {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, 303 | {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, 304 | {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, 305 | {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, 306 | {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, 307 | {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, 308 | {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, 309 | {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, 310 | {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, 311 | ] 312 | 313 | [[package]] 314 | name = "openai" 315 | version = "1.3.6" 316 | description = "The official Python library for the openai API" 317 | category = "main" 318 | optional = false 319 | python-versions = ">=3.7.1" 320 | files = [ 321 | {file = "openai-1.3.6-py3-none-any.whl", hash = "sha256:a88063de50706e7a25d9d3dcc0aa4b7e1f59ad40a3b8901af28fec097abeb230"}, 322 | {file = "openai-1.3.6.tar.gz", hash = "sha256:5f1f00658474668d8ecb7e87ceed101632c66e1e57d29aa9e0b1cb21f65a72be"}, 323 | ] 324 | 325 | [package.dependencies] 326 | anyio = ">=3.5.0,<4" 327 | distro = ">=1.7.0,<2" 328 | httpx = ">=0.23.0,<1" 329 | pydantic = ">=1.9.0,<3" 330 | sniffio = "*" 331 | tqdm = ">4" 332 | typing-extensions = ">=4.5,<5" 333 | 334 | [package.extras] 335 | datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] 336 | 337 | [[package]] 338 | name = "pandas" 339 | version = "2.1.3" 340 | description = "Powerful data structures for data analysis, time series, and statistics" 341 | category = "main" 342 | optional = false 343 | python-versions = ">=3.9" 344 | files = [ 345 | {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"}, 346 | {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"}, 347 | {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"}, 348 | {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"}, 349 | {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"}, 350 | {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"}, 351 | {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"}, 352 | {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"}, 353 | {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"}, 354 | {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"}, 355 | {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"}, 356 | {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"}, 357 | {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"}, 358 | {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"}, 359 | {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"}, 360 | {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"}, 361 | {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"}, 362 | {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"}, 363 | {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"}, 364 | {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"}, 365 | {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"}, 366 | {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"}, 367 | {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"}, 368 | {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"}, 369 | {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"}, 370 | ] 371 | 372 | [package.dependencies] 373 | numpy = [ 374 | {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, 375 | {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, 376 | {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, 377 | ] 378 | python-dateutil = ">=2.8.2" 379 | pytz = ">=2020.1" 380 | tzdata = ">=2022.1" 381 | 382 | [package.extras] 383 | all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] 384 | aws = ["s3fs (>=2022.05.0)"] 385 | clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] 386 | compression = ["zstandard (>=0.17.0)"] 387 | computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] 388 | consortium-standard = ["dataframe-api-compat (>=0.1.7)"] 389 | excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] 390 | feather = ["pyarrow (>=7.0.0)"] 391 | fss = ["fsspec (>=2022.05.0)"] 392 | gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] 393 | hdf5 = ["tables (>=3.7.0)"] 394 | html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] 395 | mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] 396 | output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] 397 | parquet = ["pyarrow (>=7.0.0)"] 398 | performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] 399 | plot = ["matplotlib (>=3.6.1)"] 400 | postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] 401 | spss = ["pyreadstat (>=1.1.5)"] 402 | sql-other = ["SQLAlchemy (>=1.4.36)"] 403 | test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] 404 | xml = ["lxml (>=4.8.0)"] 405 | 406 | [[package]] 407 | name = "prompt-toolkit" 408 | version = "3.0.41" 409 | description = "Library for building powerful interactive command lines in Python" 410 | category = "main" 411 | optional = false 412 | python-versions = ">=3.7.0" 413 | files = [ 414 | {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, 415 | {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, 416 | ] 417 | 418 | [package.dependencies] 419 | wcwidth = "*" 420 | 421 | [[package]] 422 | name = "psycopg2" 423 | version = "2.9.9" 424 | description = "psycopg2 - Python-PostgreSQL Database Adapter" 425 | category = "main" 426 | optional = false 427 | python-versions = ">=3.7" 428 | files = [ 429 | {file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"}, 430 | {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, 431 | {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, 432 | {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, 433 | {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, 434 | {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, 435 | {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, 436 | {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, 437 | {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, 438 | {file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"}, 439 | {file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"}, 440 | {file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"}, 441 | {file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"}, 442 | ] 443 | 444 | [[package]] 445 | name = "pydantic" 446 | version = "2.5.2" 447 | description = "Data validation using Python type hints" 448 | category = "main" 449 | optional = false 450 | python-versions = ">=3.7" 451 | files = [ 452 | {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, 453 | {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, 454 | ] 455 | 456 | [package.dependencies] 457 | annotated-types = ">=0.4.0" 458 | pydantic-core = "2.14.5" 459 | typing-extensions = ">=4.6.1" 460 | 461 | [package.extras] 462 | email = ["email-validator (>=2.0.0)"] 463 | 464 | [[package]] 465 | name = "pydantic-core" 466 | version = "2.14.5" 467 | description = "" 468 | category = "main" 469 | optional = false 470 | python-versions = ">=3.7" 471 | files = [ 472 | {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, 473 | {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, 474 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, 475 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, 476 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, 477 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, 478 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, 479 | {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, 480 | {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, 481 | {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, 482 | {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, 483 | {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, 484 | {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, 485 | {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, 486 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, 487 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, 488 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, 489 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, 490 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, 491 | {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, 492 | {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, 493 | {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, 494 | {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, 495 | {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, 496 | {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, 497 | {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, 498 | {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, 499 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, 500 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, 501 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, 502 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, 503 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, 504 | {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, 505 | {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, 506 | {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, 507 | {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, 508 | {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, 509 | {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, 510 | {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, 511 | {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, 512 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, 513 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, 514 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, 515 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, 516 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, 517 | {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, 518 | {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, 519 | {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, 520 | {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, 521 | {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, 522 | {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, 523 | {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, 524 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, 525 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, 526 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, 527 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, 528 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, 529 | {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, 530 | {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, 531 | {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, 532 | {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, 533 | {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, 534 | {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, 535 | {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, 536 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, 537 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, 538 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, 539 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, 540 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, 541 | {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, 542 | {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, 543 | {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, 544 | {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, 545 | {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, 546 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, 547 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, 548 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, 549 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, 550 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, 551 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, 552 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, 553 | {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, 554 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, 555 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, 556 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, 557 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, 558 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, 559 | {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, 560 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, 561 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, 562 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, 563 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, 564 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, 565 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, 566 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, 567 | {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, 568 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, 569 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, 570 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, 571 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, 572 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, 573 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, 574 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, 575 | {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, 576 | {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, 577 | ] 578 | 579 | [package.dependencies] 580 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 581 | 582 | [[package]] 583 | name = "python-dateutil" 584 | version = "2.8.2" 585 | description = "Extensions to the standard Python datetime module" 586 | category = "main" 587 | optional = false 588 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 589 | files = [ 590 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 591 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 592 | ] 593 | 594 | [package.dependencies] 595 | six = ">=1.5" 596 | 597 | [[package]] 598 | name = "pytz" 599 | version = "2023.3.post1" 600 | description = "World timezone definitions, modern and historical" 601 | category = "main" 602 | optional = false 603 | python-versions = "*" 604 | files = [ 605 | {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, 606 | {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, 607 | ] 608 | 609 | [[package]] 610 | name = "six" 611 | version = "1.16.0" 612 | description = "Python 2 and 3 compatibility utilities" 613 | category = "main" 614 | optional = false 615 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 616 | files = [ 617 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 618 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 619 | ] 620 | 621 | [[package]] 622 | name = "sniffio" 623 | version = "1.3.0" 624 | description = "Sniff out which async library your code is running under" 625 | category = "main" 626 | optional = false 627 | python-versions = ">=3.7" 628 | files = [ 629 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 630 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 631 | ] 632 | 633 | [[package]] 634 | name = "spinners" 635 | version = "0.0.24" 636 | description = "Spinners for terminals" 637 | category = "main" 638 | optional = false 639 | python-versions = "*" 640 | files = [ 641 | {file = "spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98"}, 642 | {file = "spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f"}, 643 | ] 644 | 645 | [[package]] 646 | name = "sqlalchemy" 647 | version = "2.0.23" 648 | description = "Database Abstraction Library" 649 | category = "main" 650 | optional = false 651 | python-versions = ">=3.7" 652 | files = [ 653 | {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, 654 | {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, 655 | {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, 656 | {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, 657 | {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, 658 | {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, 659 | {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, 660 | {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, 661 | {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"}, 662 | {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"}, 663 | {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"}, 664 | {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"}, 665 | {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"}, 666 | {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"}, 667 | {file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"}, 668 | {file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"}, 669 | {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"}, 670 | {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"}, 671 | {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"}, 672 | {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"}, 673 | {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"}, 674 | {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"}, 675 | {file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"}, 676 | {file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"}, 677 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:14aebfe28b99f24f8a4c1346c48bc3d63705b1f919a24c27471136d2f219f02d"}, 678 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e983fa42164577d073778d06d2cc5d020322425a509a08119bdcee70ad856bf"}, 679 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0dc9031baa46ad0dd5a269cb7a92a73284d1309228be1d5935dac8fb3cae24"}, 680 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f94aeb99f43729960638e7468d4688f6efccb837a858b34574e01143cf11f89"}, 681 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:63bfc3acc970776036f6d1d0e65faa7473be9f3135d37a463c5eba5efcdb24c8"}, 682 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-win32.whl", hash = "sha256:f48ed89dd11c3c586f45e9eec1e437b355b3b6f6884ea4a4c3111a3358fd0c18"}, 683 | {file = "SQLAlchemy-2.0.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1e018aba8363adb0599e745af245306cb8c46b9ad0a6fc0a86745b6ff7d940fc"}, 684 | {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64ac935a90bc479fee77f9463f298943b0e60005fe5de2aa654d9cdef46c54df"}, 685 | {file = "SQLAlchemy-2.0.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c4722f3bc3c1c2fcc3702dbe0016ba31148dd6efcd2a2fd33c1b4897c6a19693"}, 686 | {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af79c06825e2836de21439cb2a6ce22b2ca129bad74f359bddd173f39582bf5"}, 687 | {file = "SQLAlchemy-2.0.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683ef58ca8eea4747737a1c35c11372ffeb84578d3aab8f3e10b1d13d66f2bc4"}, 688 | {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d4041ad05b35f1f4da481f6b811b4af2f29e83af253bf37c3c4582b2c68934ab"}, 689 | {file = "SQLAlchemy-2.0.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aeb397de65a0a62f14c257f36a726945a7f7bb60253462e8602d9b97b5cbe204"}, 690 | {file = "SQLAlchemy-2.0.23-cp38-cp38-win32.whl", hash = "sha256:42ede90148b73fe4ab4a089f3126b2cfae8cfefc955c8174d697bb46210c8306"}, 691 | {file = "SQLAlchemy-2.0.23-cp38-cp38-win_amd64.whl", hash = "sha256:964971b52daab357d2c0875825e36584d58f536e920f2968df8d581054eada4b"}, 692 | {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:616fe7bcff0a05098f64b4478b78ec2dfa03225c23734d83d6c169eb41a93e55"}, 693 | {file = "SQLAlchemy-2.0.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e680527245895aba86afbd5bef6c316831c02aa988d1aad83c47ffe92655e74"}, 694 | {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9585b646ffb048c0250acc7dad92536591ffe35dba624bb8fd9b471e25212a35"}, 695 | {file = "SQLAlchemy-2.0.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4895a63e2c271ffc7a81ea424b94060f7b3b03b4ea0cd58ab5bb676ed02f4221"}, 696 | {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc1d21576f958c42d9aec68eba5c1a7d715e5fc07825a629015fe8e3b0657fb0"}, 697 | {file = "SQLAlchemy-2.0.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:967c0b71156f793e6662dd839da54f884631755275ed71f1539c95bbada9aaab"}, 698 | {file = "SQLAlchemy-2.0.23-cp39-cp39-win32.whl", hash = "sha256:0a8c6aa506893e25a04233bc721c6b6cf844bafd7250535abb56cb6cc1368884"}, 699 | {file = "SQLAlchemy-2.0.23-cp39-cp39-win_amd64.whl", hash = "sha256:f3420d00d2cb42432c1d0e44540ae83185ccbbc67a6054dcc8ab5387add6620b"}, 700 | {file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"}, 701 | {file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"}, 702 | ] 703 | 704 | [package.dependencies] 705 | greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} 706 | typing-extensions = ">=4.2.0" 707 | 708 | [package.extras] 709 | aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] 710 | aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] 711 | aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] 712 | asyncio = ["greenlet (!=0.4.17)"] 713 | asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] 714 | mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] 715 | mssql = ["pyodbc"] 716 | mssql-pymssql = ["pymssql"] 717 | mssql-pyodbc = ["pyodbc"] 718 | mypy = ["mypy (>=0.910)"] 719 | mysql = ["mysqlclient (>=1.4.0)"] 720 | mysql-connector = ["mysql-connector-python"] 721 | oracle = ["cx-oracle (>=8)"] 722 | oracle-oracledb = ["oracledb (>=1.0.1)"] 723 | postgresql = ["psycopg2 (>=2.7)"] 724 | postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] 725 | postgresql-pg8000 = ["pg8000 (>=1.29.1)"] 726 | postgresql-psycopg = ["psycopg (>=3.0.7)"] 727 | postgresql-psycopg2binary = ["psycopg2-binary"] 728 | postgresql-psycopg2cffi = ["psycopg2cffi"] 729 | postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] 730 | pymysql = ["pymysql"] 731 | sqlcipher = ["sqlcipher3-binary"] 732 | 733 | [[package]] 734 | name = "tabulate" 735 | version = "0.9.0" 736 | description = "Pretty-print tabular data" 737 | category = "main" 738 | optional = false 739 | python-versions = ">=3.7" 740 | files = [ 741 | {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, 742 | {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, 743 | ] 744 | 745 | [package.extras] 746 | widechars = ["wcwidth"] 747 | 748 | [[package]] 749 | name = "termcolor" 750 | version = "2.3.0" 751 | description = "ANSI color formatting for output in terminal" 752 | category = "main" 753 | optional = false 754 | python-versions = ">=3.7" 755 | files = [ 756 | {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, 757 | {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, 758 | ] 759 | 760 | [package.extras] 761 | tests = ["pytest", "pytest-cov"] 762 | 763 | [[package]] 764 | name = "toml" 765 | version = "0.10.2" 766 | description = "Python Library for Tom's Obvious, Minimal Language" 767 | category = "main" 768 | optional = false 769 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 770 | files = [ 771 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 772 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 773 | ] 774 | 775 | [[package]] 776 | name = "tqdm" 777 | version = "4.66.1" 778 | description = "Fast, Extensible Progress Meter" 779 | category = "main" 780 | optional = false 781 | python-versions = ">=3.7" 782 | files = [ 783 | {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, 784 | {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, 785 | ] 786 | 787 | [package.dependencies] 788 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 789 | 790 | [package.extras] 791 | dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] 792 | notebook = ["ipywidgets (>=6)"] 793 | slack = ["slack-sdk"] 794 | telegram = ["requests"] 795 | 796 | [[package]] 797 | name = "typing-extensions" 798 | version = "4.8.0" 799 | description = "Backported and Experimental Type Hints for Python 3.8+" 800 | category = "main" 801 | optional = false 802 | python-versions = ">=3.8" 803 | files = [ 804 | {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, 805 | {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, 806 | ] 807 | 808 | [[package]] 809 | name = "tzdata" 810 | version = "2023.3" 811 | description = "Provider of IANA time zone data" 812 | category = "main" 813 | optional = false 814 | python-versions = ">=2" 815 | files = [ 816 | {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, 817 | {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, 818 | ] 819 | 820 | [[package]] 821 | name = "wcwidth" 822 | version = "0.2.12" 823 | description = "Measures the displayed width of unicode strings in a terminal" 824 | category = "main" 825 | optional = false 826 | python-versions = "*" 827 | files = [ 828 | {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, 829 | {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, 830 | ] 831 | 832 | [metadata] 833 | lock-version = "2.0" 834 | python-versions = "^3.9" 835 | content-hash = "d8d00e2924af244e61bed6b1d734f0067cc3dd670f2cca171bc7ce9e06d47afd" 836 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "gptsql" 3 | version = "0.1.7" 4 | description = "LLM-powered chat interface to your Postgres database" 5 | authors = ["Scott Persinger "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | halo = "^0.0.31" 11 | sqlalchemy = "^2.0.23" 12 | pandas = "^2.1.3" 13 | openai = "^1.3.6" 14 | psycopg2 = "^2.9.9" 15 | prompt-toolkit = "^3.0.41" 16 | tabulate = "^0.9.0" 17 | termcolor = "^2.3.0" 18 | toml = "^0.10.2" 19 | 20 | 21 | [build-system] 22 | requires = ["poetry-core"] 23 | build-backend = "poetry.core.masonry.api" 24 | 25 | [tool.poetry.scripts] 26 | gptsql = 'gptsql.__main__:main' 27 | --------------------------------------------------------------------------------