|
64 | 64 | from sqlalchemy.exc import ( |
65 | 65 | ConstraintColumnNotFoundError, |
66 | 66 | InvalidRequestError, |
67 | | - OperationalError |
| 67 | + OperationalError, |
| 68 | + SQLAlchemyError |
68 | 69 | ) |
69 | 70 | from sqlalchemy.ext.automap import automap_base |
70 | 71 | from sqlalchemy.orm import Session, load_only |
@@ -312,8 +313,12 @@ def get(self, identifier, crs_transform_spec=None, **kwargs): |
312 | 313 | # Execute query within self-closing database Session context |
313 | 314 | with Session(self._engine) as session: |
314 | 315 | # Retrieve data from database as feature |
315 | | - item = session.get(self.table_model, identifier) |
316 | | - if item is None: |
| 316 | + try: |
| 317 | + item = session.get(self.table_model, identifier) |
| 318 | + # Ensure that item is not None |
| 319 | + assert item is not None |
| 320 | + except (AssertionError, SQLAlchemyError) as e: |
| 321 | + LOGGER.debug(e, exc_info=True) |
317 | 322 | msg = f'No such item: {self.id_field}={identifier}.' |
318 | 323 | raise ProviderItemNotFoundError(msg) |
319 | 324 | crs_transform_out = get_transform_from_spec(crs_transform_spec) |
@@ -827,3 +832,67 @@ def _get_bbox_filter(self, bbox: list[float]): |
827 | 832 | func.ST_GeomFromText(polygon_wkt), geom_column |
828 | 833 | ) |
829 | 834 | return bbox_filter |
| 835 | + |
| 836 | + def get(self, identifier, crs_transform_spec=None, **kwargs): |
| 837 | + """ |
| 838 | + Query the provider for a specific |
| 839 | + feature id e.g: /collections/hotosm_bdi_waterways/items/13990765 |
| 840 | +
|
| 841 | + :param identifier: feature id |
| 842 | + :param crs_transform_spec: `CrsTransformSpec` instance, optional |
| 843 | +
|
| 844 | + :returns: GeoJSON FeatureCollection |
| 845 | + """ |
| 846 | + LOGGER.debug(f'Get item by ID: {identifier}') |
| 847 | + |
| 848 | + # Execute query within self-closing database Session context |
| 849 | + with Session(self._engine) as session: |
| 850 | + # Retrieve data from database as feature |
| 851 | + try: |
| 852 | + item = session.get(self.table_model, identifier) |
| 853 | + # Ensure that item is not None |
| 854 | + assert item is not None |
| 855 | + # Ensure returned row has exact match |
| 856 | + feature_id = getattr(item, self.id_field) |
| 857 | + assert str(feature_id) == identifier |
| 858 | + except (AssertionError, SQLAlchemyError) as e: |
| 859 | + LOGGER.debug(e, exc_info=True) |
| 860 | + msg = f'No such item: {self.id_field}={identifier}.' |
| 861 | + raise ProviderItemNotFoundError(msg) |
| 862 | + crs_transform_out = get_transform_from_spec(crs_transform_spec) |
| 863 | + feature = self._sqlalchemy_to_feature(item, crs_transform_out) |
| 864 | + |
| 865 | + # Drop non-defined properties |
| 866 | + if self.properties: |
| 867 | + props = feature['properties'] |
| 868 | + dropping_keys = deepcopy(props).keys() |
| 869 | + for item in dropping_keys: |
| 870 | + if item not in self.properties: |
| 871 | + props.pop(item) |
| 872 | + |
| 873 | + # Add fields for previous and next items |
| 874 | + id_field = getattr(self.table_model, self.id_field) |
| 875 | + prev_item = ( |
| 876 | + session.query(self.table_model) |
| 877 | + .order_by(id_field.desc()) |
| 878 | + .filter(id_field < feature_id) |
| 879 | + .first() |
| 880 | + ) |
| 881 | + next_item = ( |
| 882 | + session.query(self.table_model) |
| 883 | + .order_by(id_field.asc()) |
| 884 | + .filter(id_field > feature_id) |
| 885 | + .first() |
| 886 | + ) |
| 887 | + feature['prev'] = ( |
| 888 | + getattr(prev_item, self.id_field) |
| 889 | + if prev_item is not None |
| 890 | + else feature_id |
| 891 | + ) |
| 892 | + feature['next'] = ( |
| 893 | + getattr(next_item, self.id_field) |
| 894 | + if next_item is not None |
| 895 | + else feature_id |
| 896 | + ) |
| 897 | + |
| 898 | + return feature |
0 commit comments