David Chu commited on
Commit
8a3374d
·
unverified ·
1 Parent(s): f2c42a8

feat: restful api endpoint

Browse files
Dockerfile CHANGED
@@ -26,8 +26,7 @@ RUN --mount=type=cache,target=/home/appuser/.cache/uv,uid=1001,gid=1001 \
26
  uv --cache-dir=/home/appuser/.cache/uv sync --frozen --no-install-project --no-dev
27
 
28
  COPY --chown=1001 main.py main.py
29
- COPY --chown=1001 tools/ tools/
30
- COPY --chown=1001 system_instruction.txt system_instruction.txt
31
 
32
  RUN uv sync --frozen --no-cache --no-dev --compile-bytecode
33
 
@@ -35,4 +34,4 @@ EXPOSE 8501
35
 
36
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
37
 
38
- ENTRYPOINT ["streamlit", "run", "main.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
26
  uv --cache-dir=/home/appuser/.cache/uv sync --frozen --no-install-project --no-dev
27
 
28
  COPY --chown=1001 main.py main.py
29
+ COPY --chown=1001 app/ app/
 
30
 
31
  RUN uv sync --frozen --no-cache --no-dev --compile-bytecode
32
 
 
34
 
35
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
36
 
37
+ CMD ["streamlit", "run", "main.py", "--server.port=8501", "--server.address=0.0.0.0"]
{tools → app}/__init__.py RENAMED
File without changes
app/agent.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+
5
+ from google import genai
6
+ from google.genai import types
7
+
8
+ from app.tools import dailymed, literature
9
+
10
+ SYSTEM_INSTRUCTION = (Path(__file__).parent / "system_instruction.txt").read_text()
11
+
12
+
13
+ def respond(client: genai.Client, query: str) -> list[dict]:
14
+ config = types.GenerateContentConfig(
15
+ tools=[
16
+ dailymed.find_drug_set_ids,
17
+ dailymed.find_drug_instruction,
18
+ literature.search_medical_literature,
19
+ ],
20
+ system_instruction=SYSTEM_INSTRUCTION,
21
+ )
22
+ resp = client.models.generate_content(
23
+ model="gemini-2.5-flash-preview-04-17",
24
+ contents=query,
25
+ config=config,
26
+ )
27
+
28
+ output = ((resp.text) or "").strip()
29
+
30
+ if output.startswith("```"):
31
+ # Extract content inside the first markdown code block (``` or ```json)
32
+ match = re.match(r"^```(?:json)?\s*([\s\S]*?)\s*```", output)
33
+ if match:
34
+ output = match.group(1).strip()
35
+
36
+ try:
37
+ return json.loads(output)
38
+ except json.decoder.JSONDecodeError as err:
39
+ print(err)
40
+ return [{"text": output}]
app/main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from fastapi import FastAPI
4
+ from google import genai
5
+ from pydantic import BaseModel
6
+
7
+ from app import agent
8
+
9
+ app = FastAPI()
10
+
11
+ gemini = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])
12
+
13
+
14
+ class Source(BaseModel):
15
+ title: str
16
+ url: str
17
+
18
+
19
+ class Statement(BaseModel):
20
+ text: str
21
+ sources: list[Source] | None = None
22
+
23
+
24
+ class Statements(BaseModel):
25
+ statements: list[Statement]
26
+
27
+
28
+ @app.get("/health")
29
+ def health_check():
30
+ return "ok"
31
+
32
+
33
+ @app.get("/ask", response_model=Statements)
34
+ def ask(query: str):
35
+ output = agent.respond(gemini, query)
36
+ return {"statements": output}
system_instruction.txt → app/system_instruction.txt RENAMED
File without changes
app/tools/__init__.py ADDED
File without changes
{tools → app/tools}/dailymed.py RENAMED
File without changes
{tools → app/tools}/literature.py RENAMED
File without changes
{tools → app/tools}/pubmed.py RENAMED
File without changes
main.py CHANGED
@@ -7,47 +7,14 @@ import streamlit as st
7
  from google import genai
8
  from google.genai import types
9
 
10
- from tools import dailymed, literature
11
 
12
- SYSTEM_INSTRUCTION = Path("system_instruction.txt").read_text()
13
-
14
-
15
- def respond(client: genai.Client, query: str) -> str:
16
- config = types.GenerateContentConfig(
17
- tools=[
18
- dailymed.find_drug_set_ids,
19
- dailymed.find_drug_instruction,
20
- literature.search_medical_literature,
21
- ],
22
- system_instruction=SYSTEM_INSTRUCTION,
23
- )
24
- response = client.models.generate_content(
25
- model="gemini-2.5-flash-preview-04-17",
26
- contents=query,
27
- config=config,
28
- )
29
- return response.text or ""
30
-
31
-
32
- def format_output(response: str) -> tuple[str, str]:
33
- response = response.strip()
34
-
35
- if response.startswith("```"):
36
- # Extract content inside the first markdown code block (``` or ```json)
37
- match = re.match(r"^```(?:json)?\s*([\s\S]*?)\s*```", response)
38
- if match:
39
- response = match.group(1).strip()
40
-
41
- try:
42
- statements = json.loads(response.strip())
43
- except json.decoder.JSONDecodeError as err:
44
- print(err)
45
- return response, ""
46
 
 
47
  try:
48
  answer = ""
49
  citations = {}
50
- for statement in statements:
51
  text = statement["text"].strip()
52
  answer = (
53
  answer + f"\n{text}"
@@ -65,7 +32,7 @@ def format_output(response: str) -> tuple[str, str]:
65
  answer += " ".join(f"[^{i}]" for i in sorted(citation_ids))
66
  except KeyError as err:
67
  print(err)
68
- return response, ""
69
 
70
  footnotes = "\n".join(f"[^{id}]: {citation}" for citation, id in citations.items())
71
  return answer, footnotes
@@ -83,7 +50,8 @@ def main():
83
  if submit:
84
  with st.spinner("Thinking...", show_time=True):
85
  output = respond(gemini_client, query)
86
- answer, footnotes = format_output(output)
 
87
  response.markdown(f"{answer}\n\n{footnotes}")
88
 
89
 
 
7
  from google import genai
8
  from google.genai import types
9
 
10
+ from app.agent import respond
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ def format_output(response: list[dict]) -> tuple[str, str]:
14
  try:
15
  answer = ""
16
  citations = {}
17
+ for statement in response:
18
  text = statement["text"].strip()
19
  answer = (
20
  answer + f"\n{text}"
 
32
  answer += " ".join(f"[^{i}]" for i in sorted(citation_ids))
33
  except KeyError as err:
34
  print(err)
35
+ return str(response), ""
36
 
37
  footnotes = "\n".join(f"[^{id}]: {citation}" for citation, id in citations.items())
38
  return answer, footnotes
 
50
  if submit:
51
  with st.spinner("Thinking...", show_time=True):
52
  output = respond(gemini_client, query)
53
+
54
+ answer, footnotes = format_output(output)
55
  response.markdown(f"{answer}\n\n{footnotes}")
56
 
57
 
pyproject.toml CHANGED
@@ -5,6 +5,7 @@ description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
 
8
  "google-genai>=1.14.0",
9
  "httpx>=0.28.1",
10
  "pydantic>=2.11.4",
 
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
8
+ "fastapi[standard]>=0.115.12",
9
  "google-genai>=1.14.0",
10
  "httpx>=0.28.1",
11
  "pydantic>=2.11.4",
uv.lock CHANGED
@@ -187,11 +187,21 @@ wheels = [
187
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
188
  ]
189
 
 
 
 
 
 
 
 
 
 
190
  [[package]]
191
  name = "elna"
192
  version = "0.1.0"
193
  source = { virtual = "." }
194
  dependencies = [
 
195
  { name = "google-genai" },
196
  { name = "httpx" },
197
  { name = "pydantic" },
@@ -202,6 +212,7 @@ dependencies = [
202
 
203
  [package.metadata]
204
  requires-dist = [
 
205
  { name = "google-genai", specifier = ">=1.14.0" },
206
  { name = "httpx", specifier = ">=0.28.1" },
207
  { name = "pydantic", specifier = ">=2.11.4" },
@@ -210,6 +221,62 @@ requires-dist = [
210
  { name = "tenacity", specifier = ">=9.1.2" },
211
  ]
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  [[package]]
214
  name = "fastcore"
215
  version = "1.8.2"
@@ -407,6 +474,18 @@ wheels = [
407
  { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
408
  ]
409
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  [[package]]
411
  name = "markupsafe"
412
  version = "3.0.2"
@@ -445,6 +524,15 @@ wheels = [
445
  { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
446
  ]
447
 
 
 
 
 
 
 
 
 
 
448
  [[package]]
449
  name = "narwhals"
450
  version = "1.38.2"
@@ -725,6 +813,15 @@ wheels = [
725
  { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" },
726
  ]
727
 
 
 
 
 
 
 
 
 
 
728
  [[package]]
729
  name = "python-dateutil"
730
  version = "2.9.0.post0"
@@ -840,6 +937,33 @@ wheels = [
840
  { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
841
  ]
842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  [[package]]
844
  name = "rpds-py"
845
  version = "0.24.0"
@@ -899,6 +1023,15 @@ wheels = [
899
  { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
900
  ]
901
 
 
 
 
 
 
 
 
 
 
902
  [[package]]
903
  name = "six"
904
  version = "1.17.0"
@@ -1012,6 +1145,21 @@ wheels = [
1012
  { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907, upload-time = "2024-11-22T03:06:36.71Z" },
1013
  ]
1014
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  [[package]]
1016
  name = "typing-extensions"
1017
  version = "4.13.2"
 
187
  { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
188
  ]
189
 
190
+ [[package]]
191
+ name = "dnspython"
192
+ version = "2.7.0"
193
+ source = { registry = "https://pypi.org/simple" }
194
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" }
195
+ wheels = [
196
+ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
197
+ ]
198
+
199
  [[package]]
200
  name = "elna"
201
  version = "0.1.0"
202
  source = { virtual = "." }
203
  dependencies = [
204
+ { name = "fastapi", extra = ["standard"] },
205
  { name = "google-genai" },
206
  { name = "httpx" },
207
  { name = "pydantic" },
 
212
 
213
  [package.metadata]
214
  requires-dist = [
215
+ { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
216
  { name = "google-genai", specifier = ">=1.14.0" },
217
  { name = "httpx", specifier = ">=0.28.1" },
218
  { name = "pydantic", specifier = ">=2.11.4" },
 
221
  { name = "tenacity", specifier = ">=9.1.2" },
222
  ]
223
 
224
+ [[package]]
225
+ name = "email-validator"
226
+ version = "2.2.0"
227
+ source = { registry = "https://pypi.org/simple" }
228
+ dependencies = [
229
+ { name = "dnspython" },
230
+ { name = "idna" },
231
+ ]
232
+ sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" }
233
+ wheels = [
234
+ { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" },
235
+ ]
236
+
237
+ [[package]]
238
+ name = "fastapi"
239
+ version = "0.115.12"
240
+ source = { registry = "https://pypi.org/simple" }
241
+ dependencies = [
242
+ { name = "pydantic" },
243
+ { name = "starlette" },
244
+ { name = "typing-extensions" },
245
+ ]
246
+ sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" }
247
+ wheels = [
248
+ { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" },
249
+ ]
250
+
251
+ [package.optional-dependencies]
252
+ standard = [
253
+ { name = "email-validator" },
254
+ { name = "fastapi-cli", extra = ["standard"] },
255
+ { name = "httpx" },
256
+ { name = "jinja2" },
257
+ { name = "python-multipart" },
258
+ { name = "uvicorn", extra = ["standard"] },
259
+ ]
260
+
261
+ [[package]]
262
+ name = "fastapi-cli"
263
+ version = "0.0.7"
264
+ source = { registry = "https://pypi.org/simple" }
265
+ dependencies = [
266
+ { name = "rich-toolkit" },
267
+ { name = "typer" },
268
+ { name = "uvicorn", extra = ["standard"] },
269
+ ]
270
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753, upload-time = "2024-12-15T14:28:10.028Z" }
271
+ wheels = [
272
+ { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705, upload-time = "2024-12-15T14:28:06.18Z" },
273
+ ]
274
+
275
+ [package.optional-dependencies]
276
+ standard = [
277
+ { name = "uvicorn", extra = ["standard"] },
278
+ ]
279
+
280
  [[package]]
281
  name = "fastcore"
282
  version = "1.8.2"
 
474
  { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
475
  ]
476
 
477
+ [[package]]
478
+ name = "markdown-it-py"
479
+ version = "3.0.0"
480
+ source = { registry = "https://pypi.org/simple" }
481
+ dependencies = [
482
+ { name = "mdurl" },
483
+ ]
484
+ sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
485
+ wheels = [
486
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
487
+ ]
488
+
489
  [[package]]
490
  name = "markupsafe"
491
  version = "3.0.2"
 
524
  { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
525
  ]
526
 
527
+ [[package]]
528
+ name = "mdurl"
529
+ version = "0.1.2"
530
+ source = { registry = "https://pypi.org/simple" }
531
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
532
+ wheels = [
533
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
534
+ ]
535
+
536
  [[package]]
537
  name = "narwhals"
538
  version = "1.38.2"
 
813
  { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" },
814
  ]
815
 
816
+ [[package]]
817
+ name = "pygments"
818
+ version = "2.19.1"
819
+ source = { registry = "https://pypi.org/simple" }
820
+ sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
821
+ wheels = [
822
+ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
823
+ ]
824
+
825
  [[package]]
826
  name = "python-dateutil"
827
  version = "2.9.0.post0"
 
937
  { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
938
  ]
939
 
940
+ [[package]]
941
+ name = "rich"
942
+ version = "14.0.0"
943
+ source = { registry = "https://pypi.org/simple" }
944
+ dependencies = [
945
+ { name = "markdown-it-py" },
946
+ { name = "pygments" },
947
+ ]
948
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
949
+ wheels = [
950
+ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
951
+ ]
952
+
953
+ [[package]]
954
+ name = "rich-toolkit"
955
+ version = "0.14.6"
956
+ source = { registry = "https://pypi.org/simple" }
957
+ dependencies = [
958
+ { name = "click" },
959
+ { name = "rich" },
960
+ { name = "typing-extensions" },
961
+ ]
962
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/31/b6d055f291a660a7bcaec4bcc9457b9fef8ecb6293e527b1eef1840aefd4/rich_toolkit-0.14.6.tar.gz", hash = "sha256:9dbd40e83414b84e828bf899115fff8877ce5951b73175f44db142902f07645d", size = 110805, upload-time = "2025-05-12T19:19:15.284Z" }
963
+ wheels = [
964
+ { url = "https://files.pythonhosted.org/packages/2e/3c/7a824c0514e87c61000583ac22c8321da6dc8e58a93d5f56e583482a2ee0/rich_toolkit-0.14.6-py3-none-any.whl", hash = "sha256:764f3a5f9e4b539ce805596863299e8982599514906dc5e3ccc2d390ef74c301", size = 24815, upload-time = "2025-05-12T19:19:13.713Z" },
965
+ ]
966
+
967
  [[package]]
968
  name = "rpds-py"
969
  version = "0.24.0"
 
1023
  { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
1024
  ]
1025
 
1026
+ [[package]]
1027
+ name = "shellingham"
1028
+ version = "1.5.4"
1029
+ source = { registry = "https://pypi.org/simple" }
1030
+ sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
1031
+ wheels = [
1032
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
1033
+ ]
1034
+
1035
  [[package]]
1036
  name = "six"
1037
  version = "1.17.0"
 
1145
  { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907, upload-time = "2024-11-22T03:06:36.71Z" },
1146
  ]
1147
 
1148
+ [[package]]
1149
+ name = "typer"
1150
+ version = "0.15.4"
1151
+ source = { registry = "https://pypi.org/simple" }
1152
+ dependencies = [
1153
+ { name = "click" },
1154
+ { name = "rich" },
1155
+ { name = "shellingham" },
1156
+ { name = "typing-extensions" },
1157
+ ]
1158
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559, upload-time = "2025-05-14T16:34:57.704Z" }
1159
+ wheels = [
1160
+ { url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258, upload-time = "2025-05-14T16:34:55.583Z" },
1161
+ ]
1162
+
1163
  [[package]]
1164
  name = "typing-extensions"
1165
  version = "4.13.2"