|
6 | 6 | from datacommons_client.endpoints.payloads import NodeRequestPayload
|
7 | 7 | from datacommons_client.endpoints.payloads import normalize_properties_to_string
|
8 | 8 | from datacommons_client.endpoints.response import NodeResponse
|
9 |
| -from datacommons_client.models.node import Node |
| 9 | +from datacommons_client.models.graph import Parent |
10 | 10 | from datacommons_client.utils.graph import build_ancestry_map
|
11 | 11 | from datacommons_client.utils.graph import build_ancestry_tree
|
12 | 12 | from datacommons_client.utils.graph import build_parents_dictionary
|
13 | 13 | from datacommons_client.utils.graph import fetch_parents_lru
|
14 | 14 | from datacommons_client.utils.graph import flatten_ancestry
|
15 |
| -from datacommons_client.utils.graph import Parent |
16 | 15 |
|
17 | 16 | ANCESTRY_MAX_WORKERS = 20
|
18 | 17 |
|
@@ -197,85 +196,95 @@ def fetch_all_classes(
|
197 | 196 | )
|
198 | 197 |
|
199 | 198 | def fetch_entity_parents(
|
200 |
| - self, entity_dcids: str | list[str]) -> dict[str, list[Parent]]: |
| 199 | + self, |
| 200 | + entity_dcids: str | list[str], |
| 201 | + *, |
| 202 | + as_dict: bool = True) -> dict[str, list[Parent | dict]]: |
201 | 203 | """Fetches the direct parents of one or more entities using the 'containedInPlace' property.
|
202 | 204 |
|
203 | 205 | Args:
|
204 | 206 | entity_dcids (str | list[str]): A single DCID or a list of DCIDs to query.
|
| 207 | + as_dict (bool): If True, returns a dictionary mapping each input DCID to its |
| 208 | + immediate parent entities. If False, returns a dictionary of Parent objects (which |
| 209 | + are dataclasses). |
205 | 210 |
|
206 | 211 | Returns:
|
207 |
| - dict[str, list[Parent]]: A dictionary mapping each input DCID to a list of its |
208 |
| - immediate parent entities. Each parent is represented as a Parent object, which |
209 |
| - contains the DCID, name, and type of the parent entity. |
| 212 | + dict[str, list[Parent | dict]]: A dictionary mapping each input DCID to a list of its |
| 213 | + immediate parent entities. Each parent is represented as a Parent object (which |
| 214 | + contains the DCID, name, and type of the parent entity) or as a dictionary with |
| 215 | + the same data. |
210 | 216 | """
|
211 | 217 | # Fetch property values from the API
|
212 | 218 | data = self.fetch_property_values(
|
213 | 219 | node_dcids=entity_dcids,
|
214 | 220 | properties="containedInPlace",
|
215 | 221 | ).get_properties()
|
216 | 222 |
|
217 |
| - return build_parents_dictionary(data=data) |
| 223 | + result = build_parents_dictionary(data=data) |
218 | 224 |
|
| 225 | + if as_dict: |
| 226 | + return {k: [p.to_dict() for p in v] for k, v in result.items()} |
219 | 227 |
|
220 |
| -def _fetch_parents_cached(self, dcid: str) -> tuple[Parent, ...]: |
221 |
| - """Returns cached parent nodes for a given entity using an LRU cache. |
| 228 | + return result |
222 | 229 |
|
223 |
| - This private wrapper exists because `@lru_cache` cannot be applied directly |
224 |
| - to instance methods. By passing the `NodeEndpoint` instance (`self`) as an |
225 |
| - argument caching is preserved while keeping the implementation modular and testable. |
| 230 | + def _fetch_parents_cached(self, dcid: str) -> tuple[Parent, ...]: |
| 231 | + """Returns cached parent nodes for a given entity using an LRU cache. |
226 | 232 |
|
227 |
| - Args: |
228 |
| - dcid (str): The DCID of the entity whose parents should be fetched. |
| 233 | + This private wrapper exists because `@lru_cache` cannot be applied directly |
| 234 | + to instance methods. By passing the `NodeEndpoint` instance (`self`) as an |
| 235 | + argument caching is preserved while keeping the implementation modular and testable. |
229 | 236 |
|
230 |
| - Returns: |
231 |
| - tuple[Parent, ...]: A tuple of Parent objects representing the entity's immediate parents. |
232 |
| - """ |
233 |
| - return fetch_parents_lru(self, dcid) |
234 |
| - |
235 |
| - |
236 |
| -def fetch_entity_ancestry( |
237 |
| - self, |
238 |
| - entity_dcids: str | list[str], |
239 |
| - as_tree: bool = False) -> dict[str, list[dict[str, str]] | dict]: |
240 |
| - """Fetches the full ancestry (flat or nested) for one or more entities. |
241 |
| - For each input DCID, this method builds the complete ancestry graph using a |
242 |
| - breadth-first traversal and parallel fetching. |
243 |
| - It returns either a flat list of unique parents or a nested tree structure for |
244 |
| - each entity, depending on the `as_tree` flag. The flat list matches the structure |
245 |
| - of the `/api/place/parent` endpoint of the DC website. |
246 |
| - Args: |
247 |
| - entity_dcids (str | list[str]): One or more DCIDs of the entities whose ancestry |
248 |
| - will be fetched. |
249 |
| - as_tree (bool): If True, returns a nested tree structure; otherwise, returns a flat list. |
250 |
| - Defaults to False. |
251 |
| - Returns: |
252 |
| - dict[str, list[dict[str, str]] | dict]: A dictionary mapping each input DCID to either: |
253 |
| - - A flat list of parent dictionaries (if `as_tree` is False), or |
254 |
| - - A nested ancestry tree (if `as_tree` is True). Each parent is represented by |
255 |
| - a dict with 'dcid', 'name', and 'type'. |
256 |
| - """ |
| 237 | + Args: |
| 238 | + dcid (str): The DCID of the entity whose parents should be fetched. |
| 239 | +
|
| 240 | + Returns: |
| 241 | + tuple[Parent, ...]: A tuple of Parent objects representing the entity's immediate parents. |
| 242 | + """ |
| 243 | + return fetch_parents_lru(self, dcid) |
| 244 | + |
| 245 | + def fetch_entity_ancestry( |
| 246 | + self, |
| 247 | + entity_dcids: str | list[str], |
| 248 | + as_tree: bool = False) -> dict[str, list[dict[str, str]] | dict]: |
| 249 | + """Fetches the full ancestry (flat or nested) for one or more entities. |
| 250 | + For each input DCID, this method builds the complete ancestry graph using a |
| 251 | + breadth-first traversal and parallel fetching. |
| 252 | + It returns either a flat list of unique parents or a nested tree structure for |
| 253 | + each entity, depending on the `as_tree` flag. The flat list matches the structure |
| 254 | + of the `/api/place/parent` endpoint of the DC website. |
| 255 | + Args: |
| 256 | + entity_dcids (str | list[str]): One or more DCIDs of the entities whose ancestry |
| 257 | + will be fetched. |
| 258 | + as_tree (bool): If True, returns a nested tree structure; otherwise, returns a flat list. |
| 259 | + Defaults to False. |
| 260 | + Returns: |
| 261 | + dict[str, list[dict[str, str]] | dict]: A dictionary mapping each input DCID to either: |
| 262 | + - A flat list of parent dictionaries (if `as_tree` is False), or |
| 263 | + - A nested ancestry tree (if `as_tree` is True). Each parent is represented by |
| 264 | + a dict with 'dcid', 'name', and 'type'. |
| 265 | + """ |
257 | 266 |
|
258 |
| - if isinstance(entity_dcids, str): |
259 |
| - entity_dcids = [entity_dcids] |
260 |
| - |
261 |
| - result = {} |
262 |
| - |
263 |
| - # Use a thread pool to fetch ancestry graphs in parallel for each input entity |
264 |
| - with ThreadPoolExecutor(max_workers=ANCESTRY_MAX_WORKERS) as executor: |
265 |
| - futures = [ |
266 |
| - executor.submit(build_ancestry_map, |
267 |
| - root=dcid, |
268 |
| - fetch_fn=self._fetch_parents_cached) |
269 |
| - for dcid in entity_dcids |
270 |
| - ] |
271 |
| - |
272 |
| - # Gather ancestry maps and postprocess into flat or nested form |
273 |
| - for future in futures: |
274 |
| - dcid, ancestry = future.result() |
275 |
| - if as_tree: |
276 |
| - ancestry = build_ancestry_tree(dcid, ancestry) |
277 |
| - else: |
278 |
| - ancestry = flatten_ancestry(ancestry) |
279 |
| - result[dcid] = ancestry |
280 |
| - |
281 |
| - return result |
| 267 | + if isinstance(entity_dcids, str): |
| 268 | + entity_dcids = [entity_dcids] |
| 269 | + |
| 270 | + result = {} |
| 271 | + |
| 272 | + # Use a thread pool to fetch ancestry graphs in parallel for each input entity |
| 273 | + with ThreadPoolExecutor(max_workers=ANCESTRY_MAX_WORKERS) as executor: |
| 274 | + futures = [ |
| 275 | + executor.submit(build_ancestry_map, |
| 276 | + root=dcid, |
| 277 | + fetch_fn=self._fetch_parents_cached) |
| 278 | + for dcid in entity_dcids |
| 279 | + ] |
| 280 | + |
| 281 | + # Gather ancestry maps and postprocess into flat or nested form |
| 282 | + for future in futures: |
| 283 | + dcid, ancestry = future.result() |
| 284 | + if as_tree: |
| 285 | + ancestry = build_ancestry_tree(dcid, ancestry) |
| 286 | + else: |
| 287 | + ancestry = flatten_ancestry(ancestry) |
| 288 | + result[dcid] = ancestry |
| 289 | + |
| 290 | + return result |
0 commit comments