cjber commited on
Commit
4141cd4
·
1 Parent(s): c1688dc

feat: add streamlit frontend

Browse files
Files changed (4) hide show
  1. .gitignore +2 -0
  2. app.py +137 -93
  3. pyproject.toml +3 -1
  4. uv.lock +62 -0
.gitignore CHANGED
@@ -1,6 +1,8 @@
1
  .aider*
2
  .envrc
3
 
 
 
4
  chroma_themesdb/
5
  data/
6
  notes/
 
1
  .aider*
2
  .envrc
3
 
4
+ .streamlit/secrets.toml
5
+ auth/
6
  chroma_themesdb/
7
  data/
8
  notes/
app.py CHANGED
@@ -1,114 +1,158 @@
 
1
  import py7zr
2
  import streamlit as st
 
3
 
4
  from planning_ai.common.utils import Paths
5
  from planning_ai.main import main as report_main
6
  from planning_ai.preprocessing.azure_doc import azure_process_pdfs
7
  from planning_ai.preprocessing.gcpt3 import main as preprocess_main
8
 
 
 
 
 
 
 
 
 
 
9
  UPLOAD_DIR = Paths.RAW / "gcpt3"
10
 
 
 
 
 
 
11
  if "files_extracted" not in st.session_state:
12
  st.session_state["files_extracted"] = False
 
 
13
 
14
- st.title("Report Builder")
15
-
 
16
 
17
- st.header("Upload JDL response `.json` files")
18
- st.write(
19
- "Upload your `.json` files here as a `7zip` file, they will be saved to the `data/raw/gcpt3` directory."
20
- )
21
 
22
- with st.expander("File Format"):
23
  st.write(
24
- """
25
- The `.json` files should look like the following:
26
-
27
- ```json
28
- {
29
- "id": 10008,
30
- "method": "Paper",
31
- "respondentpostcode": "CB2 9NE",
32
- "text": "",
33
- "attachments": [
34
- {
35
- "id": 3803,
36
- "url": "http:\/\/www.cambridge.gov.uk\/public\/ldf\/localplan2031\/15417.pdf",
37
- "published": false
38
- }
39
- ],
40
- "representations": [
41
- {
42
- "id": 15417,
43
- "support\/object": "Object",
44
- "document": "Issues and Options Report",
45
- "documentelementid": 29785,
46
- "documentelementtitle": "3 - Spatial Strategy, Question 3.10",
47
- "summary": "No more green belt taken away, which is prime agricultural land. Noise pollution & light pollution for surrounding villages and new houses being built, no bus services either!"
48
- },
49
- ]
50
- }
51
- ```
52
- """
53
  )
54
- if uploaded_file := st.file_uploader("Choose a `.7z` file:", type="7z"):
55
- with st.spinner("Extracting files..."):
56
- try:
57
- with py7zr.SevenZipFile(uploaded_file, mode="r") as archive:
58
- archive.extractall(path=UPLOAD_DIR)
59
- st.session_state["files_extracted"] = True
60
- st.success(
61
- f"Extracted `{len(list(UPLOAD_DIR.glob('*.json')))}` files to `{UPLOAD_DIR}`."
62
- )
63
- except Exception as e:
64
- st.error(f"Failed to extract files {e}")
65
-
66
- if not st.session_state["files_extracted"]:
67
- st.write("No files uploaded yet.")
68
-
69
- st.write("---")
70
-
71
- if st.session_state["files_extracted"]:
72
- st.title("Build Report")
73
- st.write(
74
- "Once the files are extracted, click the button below to build the report."
75
- )
76
- if st.button("Build Report", type="primary"):
77
- with st.spinner("Preprocessing files..."):
78
- try:
79
- preprocess_main()
80
- st.success("Preprocessing completed successfully!")
81
- except Exception as e:
82
- st.error(f"An error occurred during preprocessing: {e}")
83
- with st.spinner("Extracting text from PDFs..."):
 
 
 
 
 
84
  try:
85
- azure_process_pdfs()
86
- st.success("Text extraction completed successfully!")
87
- except Exception as e:
88
- st.error(f"An error occurred during PDF text extraction: {e}")
89
- with st.spinner("Building report..."):
90
- report_main()
91
- report_path = Paths.SUMMARY / "Summary_Documents.pdf"
92
- summaries_path = Paths.SUMMARY / "Summary_of_Submitted_Responses.pdf"
93
 
94
- if report_path.exists() and summaries_path.exists():
 
 
95
  st.success(
96
- "Report built successfully! Please click download buttons below."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  )
98
- col1, col2 = st.columns(2)
99
- with col1:
100
- with open(summaries_path, "rb") as docx_file:
101
- st.download_button(
102
- label="Download Report",
103
- data=docx_file,
104
- file_name="Summary_of_Submitted_Responses.docx",
105
- mime="application/docx",
106
- )
107
- with col2:
108
- with open(report_path, "rb") as docx_file:
109
- st.download_button(
110
- label="Download Summaries Report",
111
- data=docx_file,
112
- file_name="Summary_Documents.docx",
113
- mime="application/docx",
114
- )
 
1
+ import polars as pl
2
  import py7zr
3
  import streamlit as st
4
+ import streamlit_authenticator as stauth
5
 
6
  from planning_ai.common.utils import Paths
7
  from planning_ai.main import main as report_main
8
  from planning_ai.preprocessing.azure_doc import azure_process_pdfs
9
  from planning_ai.preprocessing.gcpt3 import main as preprocess_main
10
 
11
+ auth = st.secrets.to_dict()
12
+
13
+ authenticator = stauth.Authenticate(
14
+ auth["credentials"],
15
+ auth["cookie"]["name"],
16
+ auth["cookie"]["key"],
17
+ auth["cookie"]["expiry_days"],
18
+ )
19
+
20
  UPLOAD_DIR = Paths.RAW / "gcpt3"
21
 
22
+ try:
23
+ authenticator.login()
24
+ except Exception as e:
25
+ st.error(e)
26
+
27
  if "files_extracted" not in st.session_state:
28
  st.session_state["files_extracted"] = False
29
+ if "completed" not in st.session_state:
30
+ st.session_state["completed"] = False
31
 
32
+ if st.session_state["authentication_status"]:
33
+ authenticator.logout()
34
+ st.write("---")
35
 
36
+ st.title("Report Builder")
 
 
 
37
 
38
+ st.header("Upload JDL response `.json` files")
39
  st.write(
40
+ "Upload your `.json` files here as a `7zip` file, they will be saved to the `data/raw/gcpt3` directory."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  )
42
+
43
+ with st.expander("File Format"):
44
+ st.write(
45
+ """
46
+ The `.json` files should look like the following:
47
+
48
+ ```json
49
+ {
50
+ "id": 10008,
51
+ "method": "Paper",
52
+ "respondentpostcode": "CB2 9NE",
53
+ "text": "",
54
+ "attachments": [
55
+ {
56
+ "id": 3803,
57
+ "url": "http:\/\/www.cambridge.gov.uk\/public\/ldf\/localplan2031\/15417.pdf",
58
+ "published": false
59
+ }
60
+ ],
61
+ "representations": [
62
+ {
63
+ "id": 15417,
64
+ "support\/object": "Object",
65
+ "document": "Issues and Options Report",
66
+ "documentelementid": 29785,
67
+ "documentelementtitle": "3 - Spatial Strategy, Question 3.10",
68
+ "summary": "No more green belt taken away, which is prime agricultural land. Noise pollution & light pollution for surrounding villages and new houses being built, no bus services either!"
69
+ },
70
+ ]
71
+ }
72
+ ```
73
+ """
74
+ )
75
+ if uploaded_file := st.file_uploader("Choose a `.7z` file:", type="7z"):
76
+ with st.spinner("Extracting files..."):
77
  try:
78
+ # remove old files
79
+ _ = [file.unlink() for file in UPLOAD_DIR.glob("*.json")]
 
 
 
 
 
 
80
 
81
+ with py7zr.SevenZipFile(uploaded_file, mode="r") as archive:
82
+ archive.extractall(path=UPLOAD_DIR)
83
+ st.session_state["files_extracted"] = True
84
  st.success(
85
+ f"Extracted `{len(list(UPLOAD_DIR.glob('*.json')))}` files to `{UPLOAD_DIR}`."
86
+ )
87
+ except Exception as e:
88
+ st.error(f"Failed to extract files {e}")
89
+
90
+ if not st.session_state["files_extracted"]:
91
+ st.write("No files uploaded yet.")
92
+
93
+ st.write("---")
94
+
95
+ if st.session_state["files_extracted"] and not st.session_state["completed"]:
96
+ st.title("Build Report")
97
+ st.write(
98
+ "Once the files are extracted, click the button below to build the report."
99
+ )
100
+ if st.button("Build Report", type="primary"):
101
+ with st.spinner("Preprocessing files..."):
102
+ try:
103
+ preprocess_main()
104
+ st.success("Preprocessing completed successfully!")
105
+ except Exception as e:
106
+ st.error(f"An error occurred during preprocessing: {e}")
107
+ with st.spinner("Extracting text from PDFs..."):
108
+ try:
109
+ azure_process_pdfs()
110
+ st.success("Text extraction completed successfully!")
111
+ except Exception as e:
112
+ st.error(f"An error occurred during PDF text extraction: {e}")
113
+ with st.spinner("Building report..."):
114
+ representations_documents = report_main()
115
+ st.session_state["completed"] = True
116
+ elif st.session_state["authentication_status"] is False:
117
+ st.error("Username/password is incorrect")
118
+ elif st.session_state["authentication_status"] is None:
119
+ st.warning("Please enter your username and password")
120
+
121
+ if st.session_state["completed"]:
122
+ representations_documents = (
123
+ pl.read_parquet(Paths.STAGING / "gcpt3.parquet")["representations_document"]
124
+ .unique()
125
+ .to_list()
126
+ )
127
+ representations_documents_dash = [
128
+ "-".join(rep.split(" ")) for rep in representations_documents
129
+ ]
130
+
131
+ st.success("Reports built successfully! Please click download buttons below.")
132
+ for rep_dash, rep in zip(representations_documents_dash, representations_documents):
133
+ report_path = Paths.SUMMARY / f"Summary_Documents-{rep_dash}.pdf"
134
+ summaries_path = (
135
+ Paths.SUMMARY / f"Summary_of_Submitted_Responses-{rep_dash}.pdf"
136
+ )
137
+
138
+ col1, col2 = st.columns(2, border=True)
139
+ with col1:
140
+ with open(summaries_path, "rb") as pdf_file:
141
+ st.markdown("**Representations Summary Download**")
142
+ st.download_button(
143
+ label=f"{rep}",
144
+ data=pdf_file,
145
+ file_name=f"Summary_of_Submitted_Responses-{rep_dash}.pdf",
146
+ mime="application/pdf",
147
+ type="primary",
148
+ )
149
+ with col2:
150
+ with open(report_path, "rb") as pdf_file:
151
+ st.markdown("**Executive Report Download**")
152
+ st.download_button(
153
+ label=f"{rep}",
154
+ data=pdf_file,
155
+ file_name=f"Summary_Documents-{rep_dash}.pdf",
156
+ mime="application/pdf",
157
+ type="primary",
158
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -27,7 +27,6 @@ dependencies = [
27
  "pypdf2>=3.0.1",
28
  "beautifulsoup4>=4.12.3",
29
  "chromadb>=0.5.23",
30
- # "langchain-chroma>=0.1.4",
31
  "pypdf>=5.1.0",
32
  "geopandas>=1.0.1",
33
  "azure-ai-documentintelligence>=1.0.0",
@@ -36,6 +35,9 @@ dependencies = [
36
  "mapclassify>=2.8.1",
37
  "streamlit>=1.41.1",
38
  "py7zr>=0.22.0",
 
 
 
39
  ]
40
 
41
  [tool.uv]
 
27
  "pypdf2>=3.0.1",
28
  "beautifulsoup4>=4.12.3",
29
  "chromadb>=0.5.23",
 
30
  "pypdf>=5.1.0",
31
  "geopandas>=1.0.1",
32
  "azure-ai-documentintelligence>=1.0.0",
 
35
  "mapclassify>=2.8.1",
36
  "streamlit>=1.41.1",
37
  "py7zr>=0.22.0",
38
+ "streamlit-authenticator>=0.4.1",
39
+ "en_core_web_lg @ https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl"
40
+
41
  ]
42
 
43
  [tool.uv]
uv.lock CHANGED
@@ -322,6 +322,18 @@ wheels = [
322
  { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 },
323
  ]
324
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  [[package]]
326
  name = "catalogue"
327
  version = "2.0.10"
@@ -635,6 +647,14 @@ wheels = [
635
  { url = "https://files.pythonhosted.org/packages/4c/a3/ac312faeceffd2d8f86bc6dcb5c401188ba5a01bc88e69bed97578a0dfcd/durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38", size = 3461 },
636
  ]
637
 
 
 
 
 
 
 
 
 
638
  [[package]]
639
  name = "eval-type-backport"
640
  version = "0.2.2"
@@ -662,6 +682,18 @@ wheels = [
662
  { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 },
663
  ]
664
 
 
 
 
 
 
 
 
 
 
 
 
 
665
  [[package]]
666
  name = "fastapi"
667
  version = "0.115.8"
@@ -2044,6 +2076,7 @@ dependencies = [
2044
  { name = "azure-ai-documentintelligence" },
2045
  { name = "beautifulsoup4" },
2046
  { name = "chromadb" },
 
2047
  { name = "fastexcel" },
2048
  { name = "geopandas" },
2049
  { name = "langchain-community" },
@@ -2069,6 +2102,7 @@ dependencies = [
2069
  { name = "spacy" },
2070
  { name = "spacytextblob" },
2071
  { name = "streamlit" },
 
2072
  { name = "tabulate" },
2073
  { name = "transformers" },
2074
  ]
@@ -2084,6 +2118,7 @@ requires-dist = [
2084
  { name = "azure-ai-documentintelligence", specifier = ">=1.0.0" },
2085
  { name = "beautifulsoup4", specifier = ">=4.12.3" },
2086
  { name = "chromadb", specifier = ">=0.5.23" },
 
2087
  { name = "fastexcel", specifier = ">=0.11.6" },
2088
  { name = "geopandas", specifier = ">=1.0.1" },
2089
  { name = "langchain-community", specifier = ">=0.2.16" },
@@ -2109,6 +2144,7 @@ requires-dist = [
2109
  { name = "spacy", specifier = ">=3.7.6" },
2110
  { name = "spacytextblob", specifier = ">=4.0.0" },
2111
  { name = "streamlit", specifier = ">=1.41.1" },
 
2112
  { name = "tabulate", specifier = ">=0.9.0" },
2113
  { name = "transformers", specifier = ">=4.44.2" },
2114
  ]
@@ -2485,6 +2521,15 @@ wheels = [
2485
  { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
2486
  ]
2487
 
 
 
 
 
 
 
 
 
 
2488
  [[package]]
2489
  name = "pyogrio"
2490
  version = "0.10.0"
@@ -3171,6 +3216,23 @@ wheels = [
3171
  { url = "https://files.pythonhosted.org/packages/c2/87/b2e162869500062a94dde7589c167367b5538dab6eacce2e7c0f00d5c9c5/streamlit-1.41.1-py2.py3-none-any.whl", hash = "sha256:0def00822480071d642e6df36cd63c089f991da3a69fd9eb4ab8f65ce27de4e0", size = 9100386 },
3172
  ]
3173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3174
  [[package]]
3175
  name = "sympy"
3176
  version = "1.13.3"
 
322
  { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 },
323
  ]
324
 
325
+ [[package]]
326
+ name = "captcha"
327
+ version = "0.6.0"
328
+ source = { registry = "https://pypi.org/simple" }
329
+ dependencies = [
330
+ { name = "pillow" },
331
+ ]
332
+ sdist = { url = "https://files.pythonhosted.org/packages/48/4c/547742298d99408a7172aa5b1328b6bb9d85234b4082e0fd379f9b893615/captcha-0.6.0.tar.gz", hash = "sha256:a5ead7679cd8477bc636524d54f50e092a4b6f3db4e52bb98a689593638d1772", size = 187610 }
333
+ wheels = [
334
+ { url = "https://files.pythonhosted.org/packages/c0/f2/9af6abd7b07e4709a7d698fee6fa7ce7fb9814886b18de61580311b0fe96/captcha-0.6.0-py3-none-any.whl", hash = "sha256:148b2475be00a067b6173b7796cdc2332dbcb6fd4bfced1055bc8f69846bc924", size = 102241 },
335
+ ]
336
+
337
  [[package]]
338
  name = "catalogue"
339
  version = "2.0.10"
 
647
  { url = "https://files.pythonhosted.org/packages/4c/a3/ac312faeceffd2d8f86bc6dcb5c401188ba5a01bc88e69bed97578a0dfcd/durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38", size = 3461 },
648
  ]
649
 
650
+ [[package]]
651
+ name = "en-core-web-lg"
652
+ version = "3.8.0"
653
+ source = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl" }
654
+ wheels = [
655
+ { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl", hash = "sha256:293e9547a655b25499198ab15a525b05b9407a75f10255e405e8c3854329ab63" },
656
+ ]
657
+
658
  [[package]]
659
  name = "eval-type-backport"
660
  version = "0.2.2"
 
682
  { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 },
683
  ]
684
 
685
+ [[package]]
686
+ name = "extra-streamlit-components"
687
+ version = "0.1.71"
688
+ source = { registry = "https://pypi.org/simple" }
689
+ dependencies = [
690
+ { name = "streamlit" },
691
+ ]
692
+ sdist = { url = "https://files.pythonhosted.org/packages/16/a7/580b13af828ef38888196f8b2c03fa97afa89cdb7946438ca5f3271e9a81/extra_streamlit_components-0.1.71.tar.gz", hash = "sha256:d18314cf2ed009f95641882b50aa3bdb11b6a0eb6403fb43dbc8af1722419617", size = 2250093 }
693
+ wheels = [
694
+ { url = "https://files.pythonhosted.org/packages/25/57/1115e9b974478fac83ba9cd79def8b3770a91b7a9001c46a76491071f2fe/extra_streamlit_components-0.1.71-py3-none-any.whl", hash = "sha256:c8e6f98446adecd3002756362e50d0669693b7673afaa89cebfced6415cc6bd3", size = 4858597 },
695
+ ]
696
+
697
  [[package]]
698
  name = "fastapi"
699
  version = "0.115.8"
 
2076
  { name = "azure-ai-documentintelligence" },
2077
  { name = "beautifulsoup4" },
2078
  { name = "chromadb" },
2079
+ { name = "en-core-web-lg" },
2080
  { name = "fastexcel" },
2081
  { name = "geopandas" },
2082
  { name = "langchain-community" },
 
2102
  { name = "spacy" },
2103
  { name = "spacytextblob" },
2104
  { name = "streamlit" },
2105
+ { name = "streamlit-authenticator" },
2106
  { name = "tabulate" },
2107
  { name = "transformers" },
2108
  ]
 
2118
  { name = "azure-ai-documentintelligence", specifier = ">=1.0.0" },
2119
  { name = "beautifulsoup4", specifier = ">=4.12.3" },
2120
  { name = "chromadb", specifier = ">=0.5.23" },
2121
+ { name = "en-core-web-lg", url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl" },
2122
  { name = "fastexcel", specifier = ">=0.11.6" },
2123
  { name = "geopandas", specifier = ">=1.0.1" },
2124
  { name = "langchain-community", specifier = ">=0.2.16" },
 
2144
  { name = "spacy", specifier = ">=3.7.6" },
2145
  { name = "spacytextblob", specifier = ">=4.0.0" },
2146
  { name = "streamlit", specifier = ">=1.41.1" },
2147
+ { name = "streamlit-authenticator", specifier = ">=0.4.1" },
2148
  { name = "tabulate", specifier = ">=0.9.0" },
2149
  { name = "transformers", specifier = ">=4.44.2" },
2150
  ]
 
2521
  { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
2522
  ]
2523
 
2524
+ [[package]]
2525
+ name = "pyjwt"
2526
+ version = "2.10.1"
2527
+ source = { registry = "https://pypi.org/simple" }
2528
+ sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 }
2529
+ wheels = [
2530
+ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 },
2531
+ ]
2532
+
2533
  [[package]]
2534
  name = "pyogrio"
2535
  version = "0.10.0"
 
3216
  { url = "https://files.pythonhosted.org/packages/c2/87/b2e162869500062a94dde7589c167367b5538dab6eacce2e7c0f00d5c9c5/streamlit-1.41.1-py2.py3-none-any.whl", hash = "sha256:0def00822480071d642e6df36cd63c089f991da3a69fd9eb4ab8f65ce27de4e0", size = 9100386 },
3217
  ]
3218
 
3219
+ [[package]]
3220
+ name = "streamlit-authenticator"
3221
+ version = "0.4.1"
3222
+ source = { registry = "https://pypi.org/simple" }
3223
+ dependencies = [
3224
+ { name = "bcrypt" },
3225
+ { name = "captcha" },
3226
+ { name = "extra-streamlit-components" },
3227
+ { name = "pyjwt" },
3228
+ { name = "pyyaml" },
3229
+ { name = "streamlit" },
3230
+ ]
3231
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/70/04c24fb2d9b372edfa2261cbec3828633bc997c4bf29310ff53cf092a647/streamlit_authenticator-0.4.1.tar.gz", hash = "sha256:d4afeec5c972d794052a5552aa570e7eb620f0cc183484a5b052304907268381", size = 31401 }
3232
+ wheels = [
3233
+ { url = "https://files.pythonhosted.org/packages/93/28/c19eae3f2b29dd4658e16d3fd3e0164af13c501493e0bc260effcfe04407/streamlit_authenticator-0.4.1-py3-none-any.whl", hash = "sha256:776136e6a6ecde1ecc9dda054a7492290199b81e19268abff307a7d508685942", size = 34747 },
3234
+ ]
3235
+
3236
  [[package]]
3237
  name = "sympy"
3238
  version = "1.13.3"