Skip to content

Commit 8b77036

Browse files
authored
handle STAC POST JSON without CQL, extend navigation links for POST (#1185)
* handle STAC POST JSON without CQL * add method and body to pagination links
1 parent 5340d17 commit 8b77036

File tree

2 files changed

+114
-60
lines changed

2 files changed

+114
-60
lines changed

pycsw/stac/api.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
420420
"""
421421

422422
cql_ops = []
423+
json_post_data2 = {}
423424

424425
if collection not in self.get_all_collections():
425426
msg = 'Invalid collection'
@@ -432,57 +433,59 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
432433
LOGGER.debug('Empty JSON payload')
433434
json_post_data = {}
434435

435-
if 'bbox' in json_post_data:
436+
json_post_data2 = deepcopy(json_post_data)
437+
438+
if 'bbox' in json_post_data2:
436439
LOGGER.debug('Detected bbox query parameter')
437440

438441
cql_ops.append({
439442
'op': 's_intersects', 'args': [{
440443
'property': 'geometry'
441444
},
442-
{'bbox': json_post_data.pop('bbox')}]
445+
{'bbox': json_post_data2.pop('bbox')}]
443446
})
444447

445-
if 'limit' in json_post_data:
448+
if 'limit' in json_post_data2:
446449
LOGGER.debug('Detected limit query parameter')
447-
args['limit'] = json_post_data.pop('limit')
450+
args['limit'] = json_post_data2.pop('limit')
448451

449-
if 'collections' in json_post_data:
452+
if 'collections' in json_post_data2:
450453
LOGGER.debug('Detected collections query parameter')
451454
cql_ops.append({
452455
'op': 'in',
453456
'args': [{
454457
'property': 'collections',
455458
},
456-
json_post_data.pop('collections')
459+
json_post_data2.pop('collections')
457460
]
458461
})
459462

460-
if 'ids' in json_post_data:
463+
if 'ids' in json_post_data2:
461464
LOGGER.debug('Detected ids query parameter')
462465
cql_ops.append({
463466
'op': 'in',
464467
'args': [{
465468
'property': 'identifier',
466469
},
467-
json_post_data.pop('ids')
470+
json_post_data2.pop('ids')
468471
]
469472
})
470473

471-
if 'intersects' in json_post_data:
474+
if 'intersects' in json_post_data2:
472475
LOGGER.debug('Detected intersects query parameter')
473476
# TODO
474477

475-
if 'datetime' in json_post_data:
476-
if '/' not in json_post_data['datetime']:
478+
if 'datetime' in json_post_data2:
479+
if '/' not in json_post_data2['datetime']:
477480
cql_ops.append({
478481
'op': '=',
479482
'args': [
480483
{'property': 'date'},
481-
json_post_data.pop('datetime')
484+
json_post_data2.pop('datetime')
482485
]
483486
})
484487
else:
485-
begin, end = json_post_data.pop('datetime').split('/')
488+
begin, end = json_post_data2.pop('datetime').split('/')
486489
if begin != '..':
487490
cql_ops.append({
488491
'op': '>=',
@@ -500,14 +503,14 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
500503
]
501504
})
502505

503-
if 'filter' in json_post_data:
506+
if 'filter' in json_post_data2:
504507
LOGGER.debug('Detected filter query parameter')
505-
json_post_data = json_post_data.pop('filter')
508+
json_post_data2 = json_post_data2.pop('filter')
506509

507-
if not json_post_data and not cql_ops:
510+
if not json_post_data2 and not cql_ops:
508511
LOGGER.debug('No JSON POST data or CQL ops')
509512
args['type'] = 'item'
510-
elif not json_post_data and cql_ops:
513+
elif not json_post_data2 and cql_ops:
511514
LOGGER.debug('No JSON POST data left')
512515
cql_ops.append({
513516
'op': '=',
@@ -516,7 +519,7 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
516519
'item'
517520
]
518521
})
519-
json_post_data = {
522+
json_post_data2 = {
520523
'op': 'and',
521524
'args': cql_ops
522525
}
@@ -530,23 +533,27 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
530533
]
531534
})
532535
LOGGER.debug('Adding STAC API query parameters to CQL2 JSON')
533-
if json_post_data.get('op') in ['and', 'or']:
534-
json_post_data['args'].extend(cql_ops)
536+
if json_post_data2.get('op') in ['and', 'or']:
537+
json_post_data2['args'].extend(cql_ops)
535538
else:
536-
op_, args_ = json_post_data.get('op'), json_post_data.get('args')
539+
op_, args_ = json_post_data2.get('op'), json_post_data2.get('args')
537540
if None not in [op_, args_]:
538541
cql_ops.append({
539542
'op': op_,
540543
'args': args_,
541544
})
542-
json_post_data = {
545+
json_post_data2 = {
543546
'op': 'and',
544547
'args': cql_ops
545548
}
546549
else:
547-
json_post_data.update(*cql_ops)
550+
json_post_data2['filter-lang'] = 'cql2-json'
551+
json_post_data2['filter'] = {
552+
'op': 'and',
553+
'args': cql_ops
554+
}
548555

549-
headers, status, response = super().items(headers_, json_post_data, args, collection)
556+
headers, status, response = super().items(headers_, json_post_data2, args, collection)
550557

551558
response = json.loads(response)
552559
response2 = deepcopy(response)
@@ -587,6 +594,11 @@ def items(self, headers_, json_post_data, args, collection='metadata:main'):
587594
links2 = []
588595

589596
for link in response2.get('links', []):
597+
if json_post_data is not None:
598+
LOGGER.debug('Adding link body')
599+
link['method'] = 'POST'
600+
link['body'] = json_post_data
601+
590602
if link['rel'] in ['alternate', 'collection']:
591603
continue
592604
link['href'] = link['href'].replace('collections/metadata:main/items', 'search')

tests/functionaltests/suites/stac_api/test_stac_api_functional.py

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -493,27 +493,27 @@ def test_items(config):
493493
assert content['numberMatched'] == 1
494494

495495
cql_json = {
496-
"filter": {
497-
"op": "and",
498-
"args": [
496+
'filter': {
497+
'op': 'and',
498+
'args': [
499499
{
500-
"op": "=",
501-
"args": [
500+
'op': '=',
501+
'args': [
502502
{
503-
"property": "parentidentifier"
503+
'property': 'parentidentifier'
504504
},
505-
"S2MSI2A"
505+
'S2MSI2A'
506506
]
507507
},
508508
{
509-
"op": "in",
510-
"args": [
509+
'op': 'in',
510+
'args': [
511511
{
512-
"property": "title"
512+
'property': 'title'
513513
},
514514
[
515-
"S2B_MSIL2A_20190910T095029_N0500_R079_T33UXP_20230430T083712.SAFE", # noqa
516-
"S2B_MSIL2A_20190910T095029_N0500_R079_T33UXQ_20230430T083712.SAFE" # noqa
515+
'S2B_MSIL2A_20190910T095029_N0500_R079_T33UXP_20230430T083712.SAFE', # noqa
516+
'S2B_MSIL2A_20190910T095029_N0500_R079_T33UXQ_20230430T083712.SAFE' # noqa
517517
]
518518
]
519519
}
@@ -609,27 +609,27 @@ def test_items(config):
609609
assert content['numberMatched'] == 1
610610

611611
cql_json = {
612-
"filter": {
613-
"op": "and",
614-
"args": [
612+
'filter': {
613+
'op': 'and',
614+
'args': [
615615
{
616-
"op": "=",
617-
"args": [
616+
'op': '=',
617+
'args': [
618618
{
619-
"property": "parentidentifier"
619+
'property': 'parentidentifier'
620620
},
621-
"S2MSI1C"
621+
'S2MSI1C'
622622
]
623623
},
624624
{
625-
"op": "s_intersects",
626-
"args": [
625+
'op': 's_intersects',
626+
'args': [
627627
{
628-
"property": "geometry"
628+
'property': 'geometry'
629629
},
630630
{
631-
"type": "Point",
632-
"coordinates": [
631+
'type': 'Point',
632+
'coordinates': [
633633
15.32,
634634
46.88
635635
]
@@ -644,27 +644,27 @@ def test_items(config):
644644
assert content['numberMatched'] == 2
645645

646646
cql_json = {
647-
"filter": {
648-
"op": "and",
649-
"args": [
647+
'filter': {
648+
'op': 'and',
649+
'args': [
650650
{
651-
"op": "=",
652-
"args": [
651+
'op': '=',
652+
'args': [
653653
{
654-
"property": "parentidentifier"
654+
'property': 'parentidentifier'
655655
},
656-
"S2MSI2A"
656+
'S2MSI2A'
657657
]
658658
},
659659
{
660-
"op": "s_intersects",
661-
"args": [
660+
'op': 's_intersects',
661+
'args': [
662662
{
663-
"property": "geometry"
663+
'property': 'geometry'
664664
},
665665
{
666-
"type": "Polygon",
667-
"coordinates": [
666+
'type': 'Polygon',
667+
'coordinates': [
668668
[
669669
[
670670
1.230469,
@@ -698,6 +698,48 @@ def test_items(config):
698698
content = json.loads(api.items({}, cql_json, {})[2])
699699
assert content['numberMatched'] == 11
700700

701+
cql_json = {
702+
'collections': [
703+
'S2MSI2A'
704+
],
705+
'intersects': {
706+
'geometry': {
707+
'type': 'Polygon',
708+
'coordinates': [
709+
[
710+
[
711+
1.230469,
712+
33.72434
713+
],
714+
[
715+
30.585938,
716+
33.72434
717+
],
718+
[
719+
30.585938,
720+
52.802761
721+
],
722+
[
723+
1.230469,
724+
52.802761
725+
],
726+
[
727+
1.230469,
728+
33.72434
729+
]
730+
]
731+
]
732+
}
733+
}
734+
}
735+
content = json.loads(api.items({}, cql_json, {})[2])
736+
assert content['numberMatched'] == 11
737+
for link in content['links']:
738+
if link['rel'] == 'root':
739+
continue
740+
assert link['method'] == 'POST'
741+
assert link['body'] == cql_json
742+
701743

702744
def test_item(config):
703745
api = STACAPI(config)

0 commit comments

Comments
 (0)