|
27 | 27 | from requests.models import HTTPError
|
28 | 28 | from typing_extensions import deprecated
|
29 | 29 |
|
| 30 | +from datahub._codegen.aspect import _Aspect |
30 | 31 | from datahub.cli import config_utils
|
31 | 32 | from datahub.configuration.common import ConfigModel, GraphError, OperationalError
|
32 | 33 | from datahub.emitter.aspect import TIMESERIES_ASPECT_MAP
|
@@ -1697,6 +1698,7 @@ def run_assertions_for_asset(
|
1697 | 1698 |
|
1698 | 1699 | return res["runAssertionsForAsset"]
|
1699 | 1700 |
|
| 1701 | + @deprecated("Use get_entities instead which returns typed aspects") |
1700 | 1702 | def get_entities_v2(
|
1701 | 1703 | self,
|
1702 | 1704 | entity_name: str,
|
@@ -1736,6 +1738,108 @@ def get_entities_v2(
|
1736 | 1738 | retval[entity_urn][aspect_key] = aspect_value
|
1737 | 1739 | return retval
|
1738 | 1740 |
|
| 1741 | + def get_entities( |
| 1742 | + self, |
| 1743 | + entity_name: str, |
| 1744 | + urns: List[str], |
| 1745 | + aspects: Optional[List[str]] = None, |
| 1746 | + with_system_metadata: bool = False, |
| 1747 | + ) -> Dict[str, Dict[str, Tuple[_Aspect, Optional[SystemMetadataClass]]]]: |
| 1748 | + """ |
| 1749 | + Get entities using the OpenAPI v3 endpoint, deserializing aspects into typed objects. |
| 1750 | +
|
| 1751 | + Args: |
| 1752 | + entity_name: The entity type name |
| 1753 | + urns: List of entity URNs to fetch |
| 1754 | + aspects: Optional list of aspect names to fetch. If None, all aspects will be fetched. |
| 1755 | + with_system_metadata: If True, return system metadata along with each aspect. |
| 1756 | +
|
| 1757 | + Returns: |
| 1758 | + A dictionary mapping URNs to a dictionary of aspect name to tuples of |
| 1759 | + (typed aspect object, system metadata). If with_system_metadata is False, |
| 1760 | + the system metadata in the tuple will be None. |
| 1761 | + """ |
| 1762 | + aspects = aspects or [] |
| 1763 | + |
| 1764 | + request_payload = [] |
| 1765 | + for urn in urns: |
| 1766 | + entity_request: Dict[str, Any] = {"urn": urn} |
| 1767 | + for aspect_name in aspects: |
| 1768 | + entity_request[aspect_name] = {} |
| 1769 | + request_payload.append(entity_request) |
| 1770 | + |
| 1771 | + headers: Dict[str, Any] = { |
| 1772 | + "Accept": "application/json", |
| 1773 | + "Content-Type": "application/json", |
| 1774 | + } |
| 1775 | + |
| 1776 | + url = f"{self.config.server}/openapi/v3/entity/{entity_name}/batchGet" |
| 1777 | + if with_system_metadata: |
| 1778 | + url += "?systemMetadata=true" |
| 1779 | + |
| 1780 | + response = self._session.post( |
| 1781 | + url, data=json.dumps(request_payload), headers=headers |
| 1782 | + ) |
| 1783 | + response.raise_for_status() |
| 1784 | + entities = response.json() |
| 1785 | + |
| 1786 | + result: Dict[str, Dict[str, Tuple[_Aspect, Optional[SystemMetadataClass]]]] = {} |
| 1787 | + |
| 1788 | + for entity in entities: |
| 1789 | + entity_urn = entity.get("urn") |
| 1790 | + if entity_urn is None: |
| 1791 | + logger.warning( |
| 1792 | + f"Missing URN in entity response: {entity}, skipping deserialization" |
| 1793 | + ) |
| 1794 | + continue |
| 1795 | + |
| 1796 | + entity_aspects: Dict[ |
| 1797 | + str, Tuple[_Aspect, Optional[SystemMetadataClass]] |
| 1798 | + ] = {} |
| 1799 | + |
| 1800 | + for aspect_name, aspect_obj in entity.items(): |
| 1801 | + if aspect_name == "urn": |
| 1802 | + continue |
| 1803 | + |
| 1804 | + aspect_class = ASPECT_NAME_MAP.get(aspect_name) |
| 1805 | + if aspect_class is None: |
| 1806 | + logger.warning( |
| 1807 | + f"Unknown aspect type {aspect_name}, skipping deserialization" |
| 1808 | + ) |
| 1809 | + continue |
| 1810 | + |
| 1811 | + aspect_value = aspect_obj.get("value") |
| 1812 | + if aspect_value is None: |
| 1813 | + logger.warning( |
| 1814 | + f"Unknown aspect value for aspect {aspect_name}, skipping deserialization" |
| 1815 | + ) |
| 1816 | + continue |
| 1817 | + |
| 1818 | + try: |
| 1819 | + post_json_obj = post_json_transform(aspect_value) |
| 1820 | + typed_aspect = aspect_class.from_obj(post_json_obj) |
| 1821 | + assert isinstance(typed_aspect, aspect_class) and isinstance( |
| 1822 | + typed_aspect, _Aspect |
| 1823 | + ) |
| 1824 | + |
| 1825 | + system_metadata = None |
| 1826 | + if with_system_metadata: |
| 1827 | + system_metadata_obj = aspect_obj.get("systemMetadata") |
| 1828 | + if system_metadata_obj: |
| 1829 | + system_metadata = SystemMetadataClass.from_obj( |
| 1830 | + system_metadata_obj |
| 1831 | + ) |
| 1832 | + |
| 1833 | + entity_aspects[aspect_name] = (typed_aspect, system_metadata) |
| 1834 | + except Exception as e: |
| 1835 | + logger.error(f"Error deserializing aspect {aspect_name}: {e}") |
| 1836 | + raise |
| 1837 | + |
| 1838 | + if entity_aspects: |
| 1839 | + result[entity_urn] = entity_aspects |
| 1840 | + |
| 1841 | + return result |
| 1842 | + |
1739 | 1843 | def upsert_custom_assertion(
|
1740 | 1844 | self,
|
1741 | 1845 | urn: Optional[str],
|
|
0 commit comments