14
14
Type ,
15
15
get_args ,
16
16
)
17
- from urllib .parse import unquote_plus , urlencode , urljoin
17
+ from urllib .parse import unquote_plus , urlencode
18
18
19
19
import attr
20
20
import jinja2
21
21
import orjson
22
- from fastapi import Request
22
+ from fastapi import HTTPException , Request
23
23
from geojson_pydantic .geometries import parse_geometry_obj
24
+ from pydantic import ValidationError
24
25
from stac_fastapi .api .models import JSONResponse
25
26
from stac_fastapi .pgstac .core import CoreCrudClient
26
27
from stac_fastapi .pgstac .extensions .filter import FiltersClient as PgSTACFiltersClient
27
28
from stac_fastapi .pgstac .models .links import ItemCollectionLinks
28
29
from stac_fastapi .pgstac .types .search import PgstacSearch
29
- from stac_fastapi .types .errors import NotFoundError
30
- from stac_fastapi .types .requests import get_base_url
31
30
from stac_fastapi .types .stac import (
32
31
Collection ,
33
32
Collections ,
@@ -275,7 +274,6 @@ class PgSTACClient(CoreCrudClient):
275
274
276
275
async def landing_page (
277
276
self ,
278
- request : Request ,
279
277
f : Optional [str ] = None ,
280
278
** kwargs ,
281
279
) -> LandingPage :
@@ -287,67 +285,9 @@ async def landing_page(
287
285
API landing page, serving as an entry point to the API.
288
286
289
287
"""
290
- base_url = get_base_url (request )
291
-
292
- landing_page = self ._landing_page (
293
- base_url = base_url ,
294
- conformance_classes = self .conformance_classes (),
295
- extension_schemas = [],
296
- )
297
-
298
- # Add Queryables link
299
- if self .extension_is_enabled ("FilterExtension" ) or self .extension_is_enabled (
300
- "SearchFilterExtension"
301
- ):
302
- landing_page ["links" ].append (
303
- {
304
- "rel" : Relations .queryables .value ,
305
- "type" : MimeTypes .jsonschema .value ,
306
- "title" : "Queryables" ,
307
- "href" : urljoin (base_url , "queryables" ),
308
- }
309
- )
310
-
311
- # Add Aggregation links
312
- if self .extension_is_enabled ("AggregationExtension" ):
313
- landing_page ["links" ].extend (
314
- [
315
- {
316
- "rel" : "aggregate" ,
317
- "type" : "application/json" ,
318
- "title" : "Aggregate" ,
319
- "href" : urljoin (base_url , "aggregate" ),
320
- },
321
- {
322
- "rel" : "aggregations" ,
323
- "type" : "application/json" ,
324
- "title" : "Aggregations" ,
325
- "href" : urljoin (base_url , "aggregations" ),
326
- },
327
- ]
328
- )
329
-
330
- # Add OpenAPI URL
331
- landing_page ["links" ].append (
332
- {
333
- "rel" : Relations .service_desc .value ,
334
- "type" : MimeTypes .openapi .value ,
335
- "title" : "OpenAPI service description" ,
336
- "href" : str (request .url_for ("openapi" )),
337
- }
338
- )
288
+ request : Request = kwargs ["request" ]
339
289
340
- # Add human readable service-doc
341
- landing_page ["links" ].append (
342
- {
343
- "rel" : Relations .service_doc .value ,
344
- "type" : MimeTypes .html .value ,
345
- "title" : "OpenAPI service documentation" ,
346
- "href" : str (request .url_for ("swagger_ui_html" )),
347
- }
348
- )
349
-
350
- landing = LandingPage (** landing_page )
290
+ landing = await super ().landing_page (** kwargs )
351
291
352
292
output_type : Optional [MimeTypes ]
353
293
if f :
@@ -476,6 +416,37 @@ async def get_collection(
476
416
477
417
return collection
478
418
419
+ async def get_item (
420
+ self ,
421
+ item_id : str ,
422
+ collection_id : str ,
423
+ request : Request ,
424
+ f : Optional [str ] = None ,
425
+ ** kwargs ,
426
+ ) -> Item :
427
+ item = await super ().get_item (item_id , collection_id , request , ** kwargs )
428
+
429
+ output_type : Optional [MimeTypes ]
430
+ if f :
431
+ output_type = MimeTypes [f ]
432
+ else :
433
+ accepted_media = [MimeTypes [v ] for v in get_args (GeoResponseType )]
434
+ output_type = accept_media_type (
435
+ request .headers .get ("accept" , "" ), accepted_media
436
+ )
437
+
438
+ if output_type == MimeTypes .html :
439
+ return create_html_response (
440
+ request ,
441
+ item ,
442
+ template_name = "item" ,
443
+ title = f"{ collection_id } /{ item_id } item" ,
444
+ )
445
+
446
+ return item
447
+
448
+ # NOTE: We can't use `super.item_collection(...)` because of the `fields` extension
449
+ # which, when used, might return a JSONResponse directly instead of a ItemCollection (TypeDict)
479
450
async def item_collection (
480
451
self ,
481
452
collection_id : str ,
@@ -493,16 +464,6 @@ async def item_collection(
493
464
f : Optional [str ] = None ,
494
465
** kwargs ,
495
466
) -> ItemCollection :
496
- output_type : Optional [MimeTypes ]
497
- if f :
498
- output_type = MimeTypes [f ]
499
- else :
500
- accepted_media = [MimeTypes [v ] for v in get_args (GeoMultiResponseType )]
501
- output_type = accept_media_type (
502
- request .headers .get ("accept" , "" ), accepted_media
503
- )
504
-
505
- # Check if collection exist
506
467
await self .get_collection (collection_id , request = request )
507
468
508
469
base_args = {
@@ -521,12 +482,30 @@ async def item_collection(
521
482
sortby = sortby ,
522
483
)
523
484
524
- search_request = self .pgstac_search_model (** clean )
485
+ try :
486
+ search_request = self .pgstac_search_model (** clean )
487
+ except ValidationError as e :
488
+ raise HTTPException (
489
+ status_code = 400 , detail = f"Invalid parameters provided { e } "
490
+ ) from e
491
+
525
492
item_collection = await self ._search_base (search_request , request = request )
526
493
item_collection ["links" ] = await ItemCollectionLinks (
527
494
collection_id = collection_id , request = request
528
495
).get_links (extra_links = item_collection ["links" ])
529
496
497
+ #######################################################################
498
+ # Custom Responses
499
+ #######################################################################
500
+ output_type : Optional [MimeTypes ]
501
+ if f :
502
+ output_type = MimeTypes [f ]
503
+ else :
504
+ accepted_media = [MimeTypes [v ] for v in get_args (GeoMultiResponseType )]
505
+ output_type = accept_media_type (
506
+ request .headers .get ("accept" , "" ), accepted_media
507
+ )
508
+
530
509
# Additional Headers for StreamingResponse
531
510
additional_headers = {}
532
511
links = item_collection .get ("links" , [])
@@ -581,45 +560,8 @@ async def item_collection(
581
560
582
561
return ItemCollection (** item_collection )
583
562
584
- async def get_item (
585
- self ,
586
- item_id : str ,
587
- collection_id : str ,
588
- request : Request ,
589
- f : Optional [str ] = None ,
590
- ** kwargs ,
591
- ) -> Item :
592
- output_type : Optional [MimeTypes ]
593
- if f :
594
- output_type = MimeTypes [f ]
595
- else :
596
- accepted_media = [MimeTypes [v ] for v in get_args (GeoResponseType )]
597
- output_type = accept_media_type (
598
- request .headers .get ("accept" , "" ), accepted_media
599
- )
600
-
601
- # Check if collection exist
602
- await self .get_collection (collection_id , request = request )
603
-
604
- search_request = self .pgstac_search_model (
605
- ids = [item_id ], collections = [collection_id ], limit = 1
606
- )
607
- item_collection = await self ._search_base (search_request , request = request )
608
- if not item_collection ["features" ]:
609
- raise NotFoundError (
610
- f"Item { item_id } in Collection { collection_id } does not exist."
611
- )
612
-
613
- if output_type == MimeTypes .html :
614
- return create_html_response (
615
- request ,
616
- item_collection ["features" ][0 ],
617
- template_name = "item" ,
618
- title = f"{ collection_id } /{ item_id } item" ,
619
- )
620
-
621
- return Item (** item_collection ["features" ][0 ])
622
-
563
+ # NOTE: We can't use `super.get_search(...)` because of the `fields` extension
564
+ # which, when used, might return a JSONResponse directly instead of a ItemCollection (TypeDict)
623
565
async def get_search (
624
566
self ,
625
567
request : Request ,
@@ -639,16 +581,6 @@ async def get_search(
639
581
f : Optional [str ] = None ,
640
582
** kwargs ,
641
583
) -> ItemCollection :
642
- output_type : Optional [MimeTypes ]
643
- if f :
644
- output_type = MimeTypes [f ]
645
- else :
646
- accepted_media = [MimeTypes [v ] for v in get_args (GeoMultiResponseType )]
647
- output_type = accept_media_type (
648
- request .headers .get ("accept" , "" ), accepted_media
649
- )
650
-
651
- # Parse request parameters
652
584
base_args = {
653
585
"collections" : collections ,
654
586
"ids" : ids ,
@@ -668,9 +600,27 @@ async def get_search(
668
600
filter_lang = filter_lang ,
669
601
)
670
602
671
- search_request = self .pgstac_search_model (** clean )
603
+ try :
604
+ search_request = self .pgstac_search_model (** clean )
605
+ except ValidationError as e :
606
+ raise HTTPException (
607
+ status_code = 400 , detail = f"Invalid parameters provided { e } "
608
+ ) from e
609
+
672
610
item_collection = await self ._search_base (search_request , request = request )
673
611
612
+ #######################################################################
613
+ # Custom Responses
614
+ #######################################################################
615
+ output_type : Optional [MimeTypes ]
616
+ if f :
617
+ output_type = MimeTypes [f ]
618
+ else :
619
+ accepted_media = [MimeTypes [v ] for v in get_args (GeoMultiResponseType )]
620
+ output_type = accept_media_type (
621
+ request .headers .get ("accept" , "" ), accepted_media
622
+ )
623
+
674
624
# Additional Headers for StreamingResponse
675
625
additional_headers = {}
676
626
links = item_collection .get ("links" , [])
@@ -720,19 +670,24 @@ async def get_search(
720
670
721
671
return ItemCollection (** item_collection )
722
672
673
+ # NOTE: We can't use `super.post_search(...)` because of the `fields` extension
674
+ # which, when used, might return a JSONResponse directly instead of a ItemCollection (TypeDict)
723
675
async def post_search (
724
676
self ,
725
677
search_request : PgstacSearch ,
726
678
request : Request ,
727
679
** kwargs ,
728
680
) -> ItemCollection :
681
+ item_collection = await self ._search_base (search_request , request = request )
682
+
683
+ #######################################################################
684
+ # Custom Responses
685
+ #######################################################################
729
686
accepted_media = [MimeTypes [v ] for v in get_args (PostMultiResponseType )]
730
687
output_type = accept_media_type (
731
688
request .headers .get ("accept" , "" ), accepted_media
732
689
)
733
690
734
- item_collection = await self ._search_base (search_request , request = request )
735
-
736
691
# Additional Headers for StreamingResponse
737
692
additional_headers = {}
738
693
links = item_collection .get ("links" , [])
0 commit comments