|
14 | 14 | from flask_login import login_required, current_user |
15 | 15 |
|
16 | 16 | from webtool.lib.helpers import error, setting_required, parse_markdown |
17 | | -from webtool.views.api_map_item import MissingMappedFieldEncoder |
18 | 17 |
|
19 | 18 | from common.lib.exceptions import QueryParametersException, JobNotFoundException, \ |
20 | 19 | QueryNeedsExplicitConfirmationException, QueryNeedsFurtherInputException, DataSetException |
|
24 | 23 | from common.lib.helpers import UserInput, call_api |
25 | 24 | from common.lib.user import User |
26 | 25 | from backend.lib.worker import BasicWorker |
| 26 | +from common.lib.item_mapping import MissingMappedField |
27 | 27 |
|
28 | 28 | component = Blueprint("toolapi", __name__) |
29 | 29 | api_ratelimit = current_app.limiter.shared_limit("3 per second", scope="api") |
|
33 | 33 |
|
34 | 34 | csv.field_size_limit(1024 * 1024 * 1024) |
35 | 35 |
|
| 36 | +def _get_search_class(modules, datasource_id): |
| 37 | + """ |
| 38 | + Look up the search/import class for a datasource. |
| 39 | +
|
| 40 | + Abstracts the ModuleCollector convention where worker keys append a suffix |
| 41 | + to the datasource ID. Most use `-search`, some (e.g. twitter-import) use |
| 42 | + `-import`. Returns None if no matching worker is found. |
| 43 | +
|
| 44 | + TODO: ModuleCollector should expose this directly. |
| 45 | + """ |
| 46 | + return ( |
| 47 | + modules.workers.get(f"{datasource_id}-search") |
| 48 | + or modules.workers.get(f"{datasource_id}-import") |
| 49 | + ) |
| 50 | + |
36 | 51 | @component.route("/api/") |
37 | 52 | @api_ratelimit |
38 | 53 | def openapi_overview(): |
@@ -1415,6 +1430,64 @@ def export_packed_dataset(key=None, component=None): |
1415 | 1430 | else: |
1416 | 1431 | return error(406, error="Dataset component unknown") |
1417 | 1432 |
|
| 1433 | +@component.route("/api/datasources/") |
| 1434 | +@api_ratelimit |
| 1435 | +@current_app.openapi.endpoint("tool") |
| 1436 | +def list_datasources(): |
| 1437 | + """ |
| 1438 | + List all available datasources with map_item support. |
| 1439 | +
|
| 1440 | + Returns all datasources that have a map_item function, including a flag |
| 1441 | + indicating if they're from Zeeschuimer. Caller can filter as needed. |
| 1442 | +
|
| 1443 | + :return: JSON object with array of datasource metadata |
| 1444 | +
|
| 1445 | + :return-schema: { |
| 1446 | + type=object, |
| 1447 | + properties={ |
| 1448 | + datasources={ |
| 1449 | + type=array, |
| 1450 | + items={ |
| 1451 | + type=object, |
| 1452 | + properties={ |
| 1453 | + id={type=string}, |
| 1454 | + name={type=string}, |
| 1455 | + has_map_item={type=boolean}, |
| 1456 | + is_from_zeeschuimer={type=boolean} |
| 1457 | + } |
| 1458 | + } |
| 1459 | + } |
| 1460 | + } |
| 1461 | + } |
| 1462 | + """ |
| 1463 | + |
| 1464 | + available = [] |
| 1465 | + for datasource_id, metadata in g.modules.datasources.items(): |
| 1466 | + search_class = _get_search_class(g.modules, datasource_id) |
| 1467 | + if not search_class: |
| 1468 | + continue |
| 1469 | + |
| 1470 | + available.append({ |
| 1471 | + "id": datasource_id, |
| 1472 | + "name": metadata.get("name", datasource_id), |
| 1473 | + "is_from_zeeschuimer": getattr(search_class, "is_from_zeeschuimer", False), |
| 1474 | + "has_map_item": hasattr(search_class, "map_item") and callable(getattr(search_class, "map_item")) |
| 1475 | + }) |
| 1476 | + |
| 1477 | + return jsonify({ |
| 1478 | + "datasources": sorted(available, key=lambda x: x["id"]) |
| 1479 | + }), 200 |
| 1480 | + |
| 1481 | +class MissingMappedFieldEncoder(json.JSONEncoder): |
| 1482 | + """Custom JSON encoder to serialize MissingMappedField objects.""" |
| 1483 | + |
| 1484 | + def default(self, obj): |
| 1485 | + if isinstance(obj, MissingMappedField): |
| 1486 | + return { |
| 1487 | + "__missing": True, |
| 1488 | + "value": obj.value |
| 1489 | + } |
| 1490 | + return super().default(obj) |
1418 | 1491 |
|
1419 | 1492 | @component.route("/api/dataset/<string:key>/items/", methods=["GET", "HEAD"]) |
1420 | 1493 | @api_ratelimit |
|
0 commit comments