datasette-libfec 0.0.1a5__py3-none-any.whl → 0.0.1a6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datasette_libfec/__init__.py +14 -2
- datasette_libfec/router.py +21 -0
- datasette_libfec/routes_export.py +7 -4
- datasette_libfec/routes_exports.py +5 -3
- datasette_libfec/routes_pages.py +8 -3
- datasette_libfec/routes_rss.py +11 -6
- datasette_libfec/routes_search.py +3 -2
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/METADATA +1 -1
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/RECORD +13 -13
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/WHEEL +0 -0
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/entry_points.txt +0 -0
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/licenses/LICENSE +0 -0
- {datasette_libfec-0.0.1a5.dist-info → datasette_libfec-0.0.1a6.dist-info}/top_level.txt +0 -0
datasette_libfec/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
from datasette import hookimpl
|
|
3
|
+
from datasette.permissions import Action
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from textwrap import dedent
|
|
5
6
|
from typing import Optional
|
|
@@ -9,7 +10,7 @@ import os
|
|
|
9
10
|
# Import route modules to trigger route registration on the shared router
|
|
10
11
|
# pylint: disable=unused-import
|
|
11
12
|
from . import routes_rss, routes_export, routes_search, routes_exports, routes_pages
|
|
12
|
-
from .router import router
|
|
13
|
+
from .router import router, LIBFEC_ACCESS_NAME
|
|
13
14
|
_ = routes_rss, routes_export, routes_search, routes_exports, routes_pages
|
|
14
15
|
|
|
15
16
|
# https://vite.dev/guide/backend-integration.html
|
|
@@ -81,4 +82,15 @@ def extra_template_vars(datasette):
|
|
|
81
82
|
return "\n".join(parts)
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
return {"datasette_libfec_vite_entry": datasette_libfec_vite_entry}
|
|
85
|
+
return {"datasette_libfec_vite_entry": datasette_libfec_vite_entry}
|
|
86
|
+
|
|
87
|
+
@hookimpl
|
|
88
|
+
def register_actions(datasette):
|
|
89
|
+
return [
|
|
90
|
+
Action(
|
|
91
|
+
name=LIBFEC_ACCESS_NAME,
|
|
92
|
+
description=(
|
|
93
|
+
"Can access libfec pages, features"
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
]
|
datasette_libfec/router.py
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
from datasette import Forbidden
|
|
1
2
|
from datasette_plugin_router import Router
|
|
3
|
+
from functools import wraps
|
|
2
4
|
|
|
3
5
|
router = Router()
|
|
6
|
+
|
|
7
|
+
LIBFEC_ACCESS_NAME = "datasette_libfec_access"
|
|
8
|
+
|
|
9
|
+
# decorator for routes, to ensure the proper permissions are checked
|
|
10
|
+
def check_permission():
|
|
11
|
+
def decorator(func):
|
|
12
|
+
@wraps(func)
|
|
13
|
+
async def wrapper(datasette, request, **kwargs):
|
|
14
|
+
result = await datasette.allowed(
|
|
15
|
+
action=LIBFEC_ACCESS_NAME, actor=request.actor
|
|
16
|
+
)
|
|
17
|
+
if not result:
|
|
18
|
+
raise Forbidden("Permission denied for datasette-libfec access")
|
|
19
|
+
return await func(datasette=datasette, request=request, **kwargs)
|
|
20
|
+
|
|
21
|
+
return wrapper
|
|
22
|
+
|
|
23
|
+
return decorator
|
|
24
|
+
|
|
@@ -5,7 +5,7 @@ from typing import Optional, List
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import uuid
|
|
7
7
|
|
|
8
|
-
from .router import router
|
|
8
|
+
from .router import router, check_permission
|
|
9
9
|
from .state import libfec_client, export_state
|
|
10
10
|
|
|
11
11
|
|
|
@@ -24,7 +24,8 @@ class ExportResponse(BaseModel):
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@router.POST("/-/api/libfec/export/start", output=ExportResponse)
|
|
27
|
-
|
|
27
|
+
@check_permission()
|
|
28
|
+
async def export_start(datasette, request, params: Body[ExportStartParams]):
|
|
28
29
|
if export_state.running:
|
|
29
30
|
return Response.json({
|
|
30
31
|
"status": "error",
|
|
@@ -72,7 +73,8 @@ async def export_start(datasette, params: Body[ExportStartParams]):
|
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
@router.GET("/-/api/libfec/export/status", output=ExportResponse)
|
|
75
|
-
|
|
76
|
+
@check_permission()
|
|
77
|
+
async def export_status(datasette, request):
|
|
76
78
|
response_data = {
|
|
77
79
|
"status": "success",
|
|
78
80
|
"message": "Export status",
|
|
@@ -101,7 +103,8 @@ async def export_status(datasette):
|
|
|
101
103
|
|
|
102
104
|
|
|
103
105
|
@router.POST("/-/api/libfec/export/cancel", output=ExportResponse)
|
|
104
|
-
|
|
106
|
+
@check_permission()
|
|
107
|
+
async def export_cancel(datasette, request):
|
|
105
108
|
if not export_state.running:
|
|
106
109
|
return Response.json({
|
|
107
110
|
"status": "error",
|
|
@@ -2,7 +2,7 @@ from pydantic import BaseModel
|
|
|
2
2
|
from datasette import Response
|
|
3
3
|
from typing import Optional, List, Literal
|
|
4
4
|
|
|
5
|
-
from .router import router
|
|
5
|
+
from .router import router, check_permission
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ExportRecord(BaseModel):
|
|
@@ -44,7 +44,8 @@ class ApiExportsListResponse(BaseModel):
|
|
|
44
44
|
message: Optional[str] = None
|
|
45
45
|
|
|
46
46
|
@router.GET("/-/api/libfec/exports$", output=ApiExportsListResponse)
|
|
47
|
-
|
|
47
|
+
@check_permission()
|
|
48
|
+
async def list_exports(datasette, request):
|
|
48
49
|
"""List all export operations from the metadata tables"""
|
|
49
50
|
db = datasette.get_database()
|
|
50
51
|
|
|
@@ -103,7 +104,8 @@ async def list_exports(datasette):
|
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
@router.GET("/-/api/libfec/exports/(?P<export_id>\\d+)")
|
|
106
|
-
|
|
107
|
+
@check_permission()
|
|
108
|
+
async def get_export_detail(datasette, request, export_id: str):
|
|
107
109
|
"""Get detailed information about a specific export"""
|
|
108
110
|
db = datasette.get_database()
|
|
109
111
|
export_id_int = int(export_id)
|
datasette_libfec/routes_pages.py
CHANGED
|
@@ -3,7 +3,7 @@ Routes for contest, candidate, and committee pages.
|
|
|
3
3
|
"""
|
|
4
4
|
from datasette import Response
|
|
5
5
|
|
|
6
|
-
from .router import router
|
|
6
|
+
from .router import router, check_permission
|
|
7
7
|
from .page_data import (
|
|
8
8
|
Candidate,
|
|
9
9
|
CandidatePageData,
|
|
@@ -15,7 +15,8 @@ from .page_data import (
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@router.GET("/-/libfec$")
|
|
18
|
-
|
|
18
|
+
@check_permission()
|
|
19
|
+
async def libfec_page(datasette, request):
|
|
19
20
|
db = datasette.get_database()
|
|
20
21
|
page_data = IndexPageData(database_name=db.name)
|
|
21
22
|
return Response.html(
|
|
@@ -31,7 +32,8 @@ async def libfec_page(datasette):
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
@router.GET("/-/libfec/filing/(?P<filing_id>[^/]+)")
|
|
34
|
-
|
|
35
|
+
@check_permission()
|
|
36
|
+
async def filing_detail_page(datasette, request, filing_id: str):
|
|
35
37
|
db = datasette.get_database()
|
|
36
38
|
filing = None
|
|
37
39
|
form_data = None
|
|
@@ -98,6 +100,7 @@ async def filing_detail_page(datasette, filing_id: str):
|
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
@router.GET("/-/libfec/contest$")
|
|
103
|
+
@check_permission()
|
|
101
104
|
async def contest_page(datasette, request):
|
|
102
105
|
"""
|
|
103
106
|
Contest page showing candidates for a specific race.
|
|
@@ -179,6 +182,7 @@ async def contest_page(datasette, request):
|
|
|
179
182
|
|
|
180
183
|
|
|
181
184
|
@router.GET("/-/libfec/candidate/(?P<candidate_id>[^/]+)$")
|
|
185
|
+
@check_permission()
|
|
182
186
|
async def candidate_page(datasette, request, candidate_id: str):
|
|
183
187
|
"""
|
|
184
188
|
Candidate detail page.
|
|
@@ -258,6 +262,7 @@ async def candidate_page(datasette, request, candidate_id: str):
|
|
|
258
262
|
|
|
259
263
|
|
|
260
264
|
@router.GET("/-/libfec/committee/(?P<committee_id>[^/]+)$")
|
|
265
|
+
@check_permission()
|
|
261
266
|
async def committee_page(datasette, request, committee_id: str):
|
|
262
267
|
"""
|
|
263
268
|
Committee detail page.
|
datasette_libfec/routes_rss.py
CHANGED
|
@@ -4,7 +4,7 @@ from datasette_plugin_router import Body
|
|
|
4
4
|
from typing import Optional, List, Literal
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
|
-
from .router import router
|
|
7
|
+
from .router import router, check_permission
|
|
8
8
|
from .state import libfec_client, rss_watcher_state
|
|
9
9
|
|
|
10
10
|
|
|
@@ -95,7 +95,8 @@ async def rss_watch_loop(
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@router.POST("/-/api/libfec/rss/start", output=RssResponse)
|
|
98
|
-
|
|
98
|
+
@check_permission()
|
|
99
|
+
async def rss_start(datasette, request, params: Body[RssStartParams]):
|
|
99
100
|
if rss_watcher_state.running:
|
|
100
101
|
return Response.json({
|
|
101
102
|
"status": "error",
|
|
@@ -157,7 +158,8 @@ async def rss_start(datasette, params: Body[RssStartParams]):
|
|
|
157
158
|
|
|
158
159
|
|
|
159
160
|
@router.POST("/-/api/libfec/rss/stop", output=RssResponse)
|
|
160
|
-
|
|
161
|
+
@check_permission()
|
|
162
|
+
async def rss_stop(datasette, request):
|
|
161
163
|
if not rss_watcher_state.running:
|
|
162
164
|
return Response.json({
|
|
163
165
|
"status": "error",
|
|
@@ -192,7 +194,8 @@ async def rss_stop(datasette):
|
|
|
192
194
|
|
|
193
195
|
|
|
194
196
|
@router.GET("/-/api/libfec/rss/status", output=RssResponse)
|
|
195
|
-
|
|
197
|
+
@check_permission()
|
|
198
|
+
async def rss_status(datasette, request):
|
|
196
199
|
config = None
|
|
197
200
|
if rss_watcher_state.running:
|
|
198
201
|
config = {
|
|
@@ -226,7 +229,8 @@ async def rss_status(datasette):
|
|
|
226
229
|
|
|
227
230
|
|
|
228
231
|
@router.GET("/-/api/libfec/rss/syncs$", output=ApiRssSyncsListResponse)
|
|
229
|
-
|
|
232
|
+
@check_permission()
|
|
233
|
+
async def list_rss_syncs(datasette, request):
|
|
230
234
|
"""List all RSS sync operations from the metadata tables"""
|
|
231
235
|
db = datasette.get_database()
|
|
232
236
|
|
|
@@ -304,7 +308,8 @@ async def list_rss_syncs(datasette):
|
|
|
304
308
|
|
|
305
309
|
|
|
306
310
|
@router.GET("/-/api/libfec/rss/syncs/(?P<sync_id>\\d+)")
|
|
307
|
-
|
|
311
|
+
@check_permission()
|
|
312
|
+
async def get_rss_sync_detail(datasette, request, sync_id: str):
|
|
308
313
|
"""Get detailed information about a specific RSS sync"""
|
|
309
314
|
db = datasette.get_database()
|
|
310
315
|
sync_id_int = int(sync_id)
|
|
@@ -3,7 +3,7 @@ from datasette import Response
|
|
|
3
3
|
from datasette_plugin_router import Body
|
|
4
4
|
from typing import Optional, List
|
|
5
5
|
|
|
6
|
-
from .router import router
|
|
6
|
+
from .router import router, check_permission
|
|
7
7
|
from .state import libfec_client
|
|
8
8
|
|
|
9
9
|
# Cache for search RPC clients (keyed by cycle)
|
|
@@ -27,7 +27,8 @@ class SearchResponse(BaseModel):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@router.POST("/-/api/libfec/search", output=SearchResponse)
|
|
30
|
-
|
|
30
|
+
@check_permission()
|
|
31
|
+
async def search(datasette, request, params: Body[SearchParams]):
|
|
31
32
|
"""Search for candidates and committees using libfec search --rpc"""
|
|
32
33
|
from .libfec_search_rpc_client import LibfecSearchRpcClient, RpcError
|
|
33
34
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
datasette_libfec/__init__.py,sha256=
|
|
1
|
+
datasette_libfec/__init__.py,sha256=PgNmktBdrcVZsGuTjWUl4IT-bW3UrfhB1U_VtdmyZhA,3579
|
|
2
2
|
datasette_libfec/libfec_client.py,sha256=ksQslHi7U-l6sUJWoBh_jnrM0s-xkThUh3AqEcR0vCA,10392
|
|
3
3
|
datasette_libfec/libfec_export_rpc_client.py,sha256=k0i7CZygs2gL4oqp2Gv9lyNTNNSqcg7TzLZrhAOiLMw,13469
|
|
4
4
|
datasette_libfec/libfec_rpc_client.py,sha256=czot8aKWNMSZQ7O7fYqIuGNwpBXczLd5PRzolFxEA9M,12351
|
|
5
5
|
datasette_libfec/libfec_search_rpc_client.py,sha256=O0vIFfNYdDz62rFcM7xt3awNJVFvGxwIbPCO1LxqECI,10232
|
|
6
6
|
datasette_libfec/manifest.json,sha256=7o6f8DV46mzmgZoopganu6RKNnjzZiIKMQ4DcJatan0,2124
|
|
7
7
|
datasette_libfec/page_data.py,sha256=Ihl3OFmuM2zz9SdyC9XY3abheBJRP6_UqxJlhYnzKyY,2303
|
|
8
|
-
datasette_libfec/router.py,sha256=
|
|
9
|
-
datasette_libfec/routes_export.py,sha256=
|
|
10
|
-
datasette_libfec/routes_exports.py,sha256=
|
|
11
|
-
datasette_libfec/routes_pages.py,sha256=
|
|
12
|
-
datasette_libfec/routes_rss.py,sha256=
|
|
13
|
-
datasette_libfec/routes_search.py,sha256=
|
|
8
|
+
datasette_libfec/router.py,sha256=t3X7mps4IOp20wHPkMygznVEdZ9bLUyYUpzclot7ah8,723
|
|
9
|
+
datasette_libfec/routes_export.py,sha256=wr5qoT9bDipLnkJr3QFC64TriLVv2CJlLSnJpajYIA8,3915
|
|
10
|
+
datasette_libfec/routes_exports.py,sha256=58pbEp-4nr9JP3BGFf1oPjf1LmAgmkGKa6aZfpK1vRs,6442
|
|
11
|
+
datasette_libfec/routes_pages.py,sha256=NvpkgNM5VFTj_oDI28-IgYgaPtJI_vsnnIojtEMRlpw,10232
|
|
12
|
+
datasette_libfec/routes_rss.py,sha256=m1oSzNjEka5opecW6qud7LRGEiKRB4hKHfb_JlaIn28,13295
|
|
13
|
+
datasette_libfec/routes_search.py,sha256=byNlYOwnL5GWj-4IYl5DZGba1VEJ6vQK223yYCtZji4,2269
|
|
14
14
|
datasette_libfec/state.py,sha256=K6cihx7jX3L2F12Hrrgv1hQ5mmQ15obvtZX_5CL2Lcs,206
|
|
15
15
|
datasette_libfec/static/gen/candidate-BEqDafKu.css,sha256=712NuY9_SD-8GFUaye6dCB5GRx7m6uiuJ48MXVlIxrY,2706
|
|
16
16
|
datasette_libfec/static/gen/candidate-tqxa29G-.js,sha256=fYF4yP-bLRS1qvOQ7U7v6b0dBVCaoUw25XyJESFI9mE,7483
|
|
@@ -27,11 +27,11 @@ datasette_libfec/static/gen/index-client-CDwZ_Ixa.js,sha256=cjpR7ee1-TP3ZaJ-yVSi
|
|
|
27
27
|
datasette_libfec/static/gen/index-jv9_YIKt.css,sha256=KIhhjpufP-cjb50w679eXk8-S_BccONp7ruIAWzU8rI,18180
|
|
28
28
|
datasette_libfec/static/gen/load-AXKAVXVj.js,sha256=fr3cnCcVvgsXBEVK-6oOddG_0a5WEC3UMAFNIeVKzbU,28394
|
|
29
29
|
datasette_libfec/templates/libfec_base.html,sha256=RJOA7qBuR1fPfWj48lfT9Gj3pS3gMGpUT4URWa-L43g,309
|
|
30
|
-
datasette_libfec-0.0.
|
|
30
|
+
datasette_libfec-0.0.1a6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
31
31
|
scripts/typegen-pagedata.py,sha256=QoBJ6QPmOCoNxe8nUe44lanfkP-HYsb4f_Dld9ljuQg,253
|
|
32
32
|
tests/test_libfec.py,sha256=-4t7DjTrBFaWiO93qSvXN4RBGPlKYslLAcPVqY-Wp_o,362
|
|
33
|
-
datasette_libfec-0.0.
|
|
34
|
-
datasette_libfec-0.0.
|
|
35
|
-
datasette_libfec-0.0.
|
|
36
|
-
datasette_libfec-0.0.
|
|
37
|
-
datasette_libfec-0.0.
|
|
33
|
+
datasette_libfec-0.0.1a6.dist-info/METADATA,sha256=0Tvzw5NPe_ZHd8OfrRHcqpmXmzPUsFqKw-M2xlf9JPg,1679
|
|
34
|
+
datasette_libfec-0.0.1a6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
35
|
+
datasette_libfec-0.0.1a6.dist-info/entry_points.txt,sha256=FCShfzjkN9mcO5oLaFz72dS-zwmRip3jhm9Pj0DJhEY,38
|
|
36
|
+
datasette_libfec-0.0.1a6.dist-info/top_level.txt,sha256=r5vRGN9jFvSIklVSeMy0LJKhjvVEhVvguvuQ-vXVv7k,31
|
|
37
|
+
datasette_libfec-0.0.1a6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|