Skip to content

Commit 8a55476

Browse files
Copilotdavidgamez
andauthored
feat: expose license_tags to feed and license endpoints (#1619)
Co-authored-by: davidgamez <1192523+davidgamez@users.noreply.github.com>
1 parent fce2e1a commit 8a55476

File tree

10 files changed

+181
-10
lines changed

10 files changed

+181
-10
lines changed

api/.openapi-generator/FILES

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
src/feeds/impl/__init__.py
22
src/feeds_gen/apis/__init__.py
3-
src/feeds_gen/apis/beta_api.py
4-
src/feeds_gen/apis/beta_api_base.py
53
src/feeds_gen/apis/datasets_api.py
64
src/feeds_gen/apis/datasets_api_base.py
75
src/feeds_gen/apis/feeds_api.py

api/src/scripts/populate_db_test_data.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
Notice,
1414
Feature,
1515
License,
16+
LicenseTag,
17+
LicenseTagGroup,
1618
t_feedsearch,
1719
Location,
1820
Officialstatushistory,
@@ -224,6 +226,66 @@ def populate_test_datasets(self, filepath, db_session: "Session"):
224226
license_obj.rules.append(rule_obj)
225227
db_session.commit()
226228

229+
# License tag groups (optional section to seed group metadata used by license_tags)
230+
if "license_tag_groups" in data:
231+
for group in data["license_tag_groups"]:
232+
group_id = group.get("id")
233+
if not group_id:
234+
continue
235+
existing_group = db_session.get(LicenseTagGroup, group_id)
236+
if existing_group:
237+
continue
238+
db_session.add(
239+
LicenseTagGroup(
240+
id=group_id,
241+
short_name=group.get("short_name"),
242+
description=group.get("description") or group_id,
243+
)
244+
)
245+
db_session.commit()
246+
247+
# License tags (optional section to seed tag metadata)
248+
if "license_tags" in data:
249+
for tag in data["license_tags"]:
250+
tag_id = tag.get("id")
251+
if not tag_id:
252+
continue
253+
existing_tag = db_session.get(LicenseTag, tag_id)
254+
if existing_tag:
255+
continue
256+
db_session.add(
257+
LicenseTag(
258+
id=tag_id,
259+
group=tag.get("group"),
260+
tag=tag.get("tag"),
261+
url=tag.get("url"),
262+
description=tag.get("description"),
263+
)
264+
)
265+
db_session.commit()
266+
267+
# License tag associations: attach tags to licenses via the many-to-many relationship
268+
if "license_license_tags" in data:
269+
for lt in data["license_license_tags"]:
270+
license_id = lt.get("license_id")
271+
tag_id = lt.get("tag_id")
272+
if not license_id or not tag_id:
273+
continue
274+
license_obj = db_session.get(License, license_id)
275+
if not license_obj:
276+
self.logger.error(
277+
f"No license found with id: {license_id};"
278+
f" skipping license_license_tag association for tag {tag_id}"
279+
)
280+
continue
281+
tag_obj = db_session.get(LicenseTag, tag_id)
282+
if not tag_obj:
283+
self.logger.error(f"No license tag found with id: {tag_id}; skipping")
284+
continue
285+
if tag_obj not in license_obj.tags:
286+
license_obj.tags.append(tag_obj)
287+
db_session.commit()
288+
227289
# GBFS version
228290
if "gbfs_versions" in data:
229291
for version in data["gbfs_versions"]:

api/src/shared/db_models/basic_feed_impl.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ def from_orm(cls, feed: Feed | None) -> BasicFeed | None:
2222
return None
2323
# Determine license_is_spdx from the related License ORM if available
2424
license_is_spdx = None
25+
license_tags = None
2526
if getattr(feed, "license", None) is not None:
2627
license_is_spdx = feed.license.is_spdx
28+
license_tags = sorted([tag.id for tag in getattr(feed.license, "tags", [])]) or None
2729

2830
return cls(
2931
id=feed.stable_id,
@@ -43,6 +45,7 @@ def from_orm(cls, feed: Feed | None) -> BasicFeed | None:
4345
license_id=feed.license_id,
4446
license_is_spdx=license_is_spdx,
4547
license_notes=feed.license_notes,
48+
license_tags=license_tags,
4649
),
4750
redirects=sorted([RedirectImpl.from_orm(item) for item in feed.redirectingids], key=lambda x: x.target_id),
4851
)

api/src/shared/db_models/license_base_impl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ def from_orm(cls, license_orm: Optional[LicenseOrm]) -> Optional[LicenseBase]:
2828
description=license_orm.description,
2929
created_at=license_orm.created_at,
3030
updated_at=license_orm.updated_at,
31+
license_tags=sorted([tag.id for tag in getattr(license_orm, "tags", [])]) or None,
3132
)

api/tests/integration/test_data/extra_test_data.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,45 @@
8080
}
8181
],
8282

83+
"license_tag_groups": [
84+
{
85+
"id": "family",
86+
"short_name": "Family",
87+
"description": "License family taxonomy"
88+
},
89+
{
90+
"id": "license",
91+
"short_name": "License",
92+
"description": "License type taxonomy"
93+
}
94+
],
95+
96+
"license_tags": [
97+
{
98+
"id": "family:ODC",
99+
"group": "family",
100+
"tag": "ODC",
101+
"description": "Open Data Commons family"
102+
},
103+
{
104+
"id": "license:open-data-commons",
105+
"group": "license",
106+
"tag": "open-data-commons",
107+
"description": "Open Data Commons license"
108+
}
109+
],
110+
111+
"license_license_tags": [
112+
{
113+
"license_id": "license-1",
114+
"tag_id": "family:ODC"
115+
},
116+
{
117+
"license_id": "license-1",
118+
"tag_id": "license:open-data-commons"
119+
}
120+
],
121+
83122
"feeds": [
84123
{
85124
"id": "mdb-60",

api/tests/integration/test_feeds_api.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -947,18 +947,23 @@ def test_gbfs_feed_id_get(client: TestClient, values):
947947

948948

949949
@pytest.mark.parametrize(
950-
"feed_id, expected_license_id, expected_is_spdx, expected_license_notes",
950+
"feed_id, expected_license_id, expected_is_spdx, expected_license_notes, expected_license_tags",
951951
[
952-
("mdb-70", "license-1", True, "Notes for license-1"),
953-
("mdb-80", "license-2", False, None),
952+
("mdb-70", "license-1", True, "Notes for license-1", ["family:ODC", "license:open-data-commons"]),
953+
("mdb-80", "license-2", False, None, None),
954954
],
955955
)
956956
def test_feeds_have_expected_license_info(
957-
client: TestClient, feed_id: str, expected_license_id: str, expected_is_spdx: bool, expected_license_notes: str
957+
client: TestClient,
958+
feed_id: str,
959+
expected_license_id: str,
960+
expected_is_spdx: bool,
961+
expected_license_notes: str,
962+
expected_license_tags: list,
958963
):
959964
"""
960965
Verify that specified feeds have the expected license id,
961-
license_is_spdx and license_notes from the test fixture.
966+
license_is_spdx, license_notes, and license_tags from the test fixture.
962967
"""
963968
response = client.request(
964969
"GET",
@@ -974,3 +979,8 @@ def test_feeds_have_expected_license_info(
974979
assert body["source_info"]["license_is_spdx"] is expected_is_spdx
975980
# Check license_notes (may be None)
976981
assert body["source_info"].get("license_notes") == expected_license_notes
982+
# Check license_tags (may be None if no tags)
983+
if expected_license_tags is None:
984+
assert body["source_info"].get("license_tags") is None
985+
else:
986+
assert sorted(body["source_info"].get("license_tags", [])) == sorted(expected_license_tags)

api/tests/integration/test_licenses_api.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@pytest.mark.parametrize(
9-
"license_id, expected_is_spdx, expected_name, expected_url, expected_description, expected_rules",
9+
"license_id, expected_is_spdx, expected_name, expected_url, expected_description, expected_rules, expected_tags",
1010
[
1111
(
1212
"license-1",
@@ -28,6 +28,7 @@
2828
"type": "condition",
2929
},
3030
],
31+
["family:ODC", "license:open-data-commons"],
3132
),
3233
(
3334
"license-2",
@@ -49,6 +50,7 @@
4950
"type": "limitation",
5051
},
5152
],
53+
None,
5254
),
5355
],
5456
)
@@ -60,6 +62,7 @@ def test_get_license_by_id(
6062
expected_url: str,
6163
expected_description: str,
6264
expected_rules: list,
65+
expected_tags: list,
6366
):
6467
"""GET /v1/licenses/{id} returns the expected license fields for known test licenses."""
6568
response = client.request("GET", f"/v1/licenses/{license_id}", headers=authHeaders)
@@ -74,6 +77,11 @@ def test_get_license_by_id(
7477
actual_rules = {rule["name"]: rule for rule in body.get("license_rules", [])}
7578
expected_rule_map = {rule["name"]: rule for rule in expected_rules}
7679
assert actual_rules == expected_rule_map
80+
# Verify license_tags
81+
if expected_tags is None:
82+
assert body.get("license_tags") is None
83+
else:
84+
assert sorted(body.get("license_tags", [])) == sorted(expected_tags)
7785

7886

7987
def test_get_licenses_list_contains_test_licenses(client: TestClient):

docs/DatabaseCatalogAPI.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ paths:
145145
description: Get GBFS feeds from the Mobility Database.
146146
tags:
147147
- "feeds"
148-
- "beta"
149148
operationId: getGbfsFeeds
150149
parameters:
151150
- $ref: "#/components/parameters/limit_query_param_gbfs_feeds_endpoint"
@@ -175,7 +174,6 @@ paths:
175174
description: Get the specified GTFS feed from the Mobility Database. Once a week, we check if the latest dataset has been updated and, if so, we update it in our system accordingly.
176175
tags:
177176
- "feeds"
178-
- "beta"
179177
operationId: getGtfsFeed
180178

181179
security:
@@ -1088,6 +1086,14 @@ components:
10881086
description: Notes concerning the relation between the feed and the license.
10891087
type: string
10901088
example: Detected locale/jurisdiction port 'nl'. SPDX does not list ported CC licenses; using canonical ID.
1089+
license_tags:
1090+
description: List of taxonomy tags associated with the feed's license.
1091+
type: array
1092+
items:
1093+
type: string
1094+
example:
1095+
- "family:ODC"
1096+
- "license:open-data-commons"
10911097

10921098
Locations:
10931099
type: array
@@ -1345,6 +1351,14 @@ components:
13451351
type: string
13461352
example: 2023-07-10T22:06:00Z
13471353
format: date-time
1354+
license_tags:
1355+
description: List of taxonomy tags associated with the license.
1356+
type: array
1357+
items:
1358+
type: string
1359+
example:
1360+
- "family:ODC"
1361+
- "license:open-data-commons"
13481362

13491363
LicenseWithRules:
13501364
allOf:

docs/OperationsAPI.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,14 @@ components:
10221022
description: Notes concerning the relation between the feed and the license.
10231023
type: string
10241024
example: Detected locale/jurisdiction port 'nl'. SPDX does not list ported CC licenses; using canonical ID.
1025+
license_tags:
1026+
description: List of taxonomy tags associated with the feed's license.
1027+
type: array
1028+
items:
1029+
type: string
1030+
example:
1031+
- "family:ODC"
1032+
- "license:open-data-commons"
10251033
Locations:
10261034
type: array
10271035
items:
@@ -1268,6 +1276,14 @@ components:
12681276
type: string
12691277
example: 2023-07-10T22:06:00Z
12701278
format: date-time
1279+
license_tags:
1280+
description: List of taxonomy tags associated with the license.
1281+
type: array
1282+
items:
1283+
type: string
1284+
example:
1285+
- "family:ODC"
1286+
- "license:open-data-commons"
12711287
LicenseWithRules:
12721288
allOf:
12731289
- $ref: "#/components/schemas/LicenseBase"
@@ -1478,6 +1494,7 @@ components:
14781494
14791495
14801496
1497+
14811498
* vp - vehicle positions
14821499
* tu - trip updates
14831500
* sa - service alerts
@@ -1599,6 +1616,7 @@ components:
15991616
16001617
16011618
1619+
16021620
* vp - vehicle positions
16031621
* tu - trip updates
16041622
* sa - service alerts
@@ -1677,6 +1695,7 @@ components:
16771695
16781696
16791697
1698+
16801699
* `active` Feed should be used in public trip planners.
16811700
* `deprecated` Feed is explicitly deprecated and should not be used in public trip planners.
16821701
* `inactive` Feed hasn't been recently updated and should be used at risk of providing outdated information.
@@ -1697,6 +1716,7 @@ components:
16971716
16981717
16991718
1719+
17001720
* `gtfs` GTFS feed.
17011721
* `gtfs_rt` GTFS-RT feed.
17021722
* `gbfs` GBFS feed.

web-app/src/app/services/feeds/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,14 @@ export interface components {
624624
* @example Detected locale/jurisdiction port 'nl'. SPDX does not list ported CC licenses; using canonical ID.
625625
*/
626626
license_notes?: string;
627+
/**
628+
* @description List of taxonomy tags associated with the feed's license.
629+
* @example [
630+
* "family:ODC",
631+
* "license:open-data-commons"
632+
* ]
633+
*/
634+
license_tags?: string[];
627635
};
628636
Locations: Array<components['schemas']['Location']>;
629637
Location: {
@@ -848,6 +856,14 @@ export interface components {
848856
* @example "2023-07-10T22:06:00.000Z"
849857
*/
850858
updated_at?: string;
859+
/**
860+
* @description List of taxonomy tags associated with the license.
861+
* @example [
862+
* "family:ODC",
863+
* "license:open-data-commons"
864+
* ]
865+
*/
866+
license_tags?: string[];
851867
};
852868
LicenseWithRules: components['schemas']['LicenseBase'] & {
853869
license_rules?: Array<components['schemas']['LicenseRule']>;

0 commit comments

Comments
 (0)