geoservercloud 0.2.3.dev3__tar.gz → 0.2.4.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geoservercloud
3
- Version: 0.2.3.dev3
3
+ Version: 0.2.4.dev2
4
4
  Summary: Lightweight Python client to interact with GeoServer Cloud REST API, GeoServer ACL and OGC services
5
5
  License: BSD-2-Clause
6
6
  Author: Camptocamp
@@ -1,4 +1,5 @@
1
1
  import os.path
2
+ from json import JSONDecodeError
2
3
  from pathlib import Path
3
4
  from typing import Any
4
5
 
@@ -92,6 +93,9 @@ class GeoServerCloud:
92
93
  return response
93
94
 
94
95
  def delete_workspace(self, workspace: str) -> Response:
96
+ """
97
+ Delete a GeoServer workspace (recursively)
98
+ """
95
99
  path: str = f"/rest/workspaces/{workspace}.json?recurse=true"
96
100
  response: Response = self.delete_request(path)
97
101
  if self.default_workspace == workspace:
@@ -112,6 +116,9 @@ class GeoServerCloud:
112
116
  )
113
117
 
114
118
  def publish_workspace(self, workspace) -> Response:
119
+ """
120
+ Publish the WMS service for a given workspace
121
+ """
115
122
  path: str = f"{self.workspace_wms_settings_path(workspace)}"
116
123
 
117
124
  data: dict[str, dict[str, Any]] = Templates.workspace_wms(workspace)
@@ -120,6 +127,9 @@ class GeoServerCloud:
120
127
  def set_default_locale_for_service(
121
128
  self, workspace: str, locale: str | None
122
129
  ) -> Response:
130
+ """
131
+ Set a default language for localized WMS requests
132
+ """
123
133
  path: str = self.workspace_wms_settings_path(workspace)
124
134
  data: dict[str, dict[str, Any]] = {
125
135
  "wms": {
@@ -129,6 +139,9 @@ class GeoServerCloud:
129
139
  return self.put_request(path, json=data)
130
140
 
131
141
  def unset_default_locale_for_service(self, workspace) -> None:
142
+ """
143
+ Remove the default language for localized WMS requests
144
+ """
132
145
  self.set_default_locale_for_service(workspace, None)
133
146
 
134
147
  def create_pg_datastore(
@@ -447,6 +460,9 @@ class GeoServerCloud:
447
460
  styles: list[str] | None = None,
448
461
  language: str | None = None,
449
462
  ) -> ResponseWrapper | None:
463
+ """
464
+ WMS GetMap request
465
+ """
450
466
  if not self.wms:
451
467
  self.create_wms()
452
468
  params: dict[str, Any] = {
@@ -478,6 +494,9 @@ class GeoServerCloud:
478
494
  styles: list[str] | None = None,
479
495
  xy: list[float] = [0, 0],
480
496
  ) -> ResponseWrapper | None:
497
+ """
498
+ WMS GetFeatureInfo request
499
+ """
481
500
  if not self.wms:
482
501
  self.create_wms()
483
502
  params = {
@@ -504,6 +523,9 @@ class GeoServerCloud:
504
523
  style: str | None = None,
505
524
  workspace: str | None = None,
506
525
  ) -> Response:
526
+ """
527
+ WMS GetLegendGraphic request
528
+ """
507
529
  path: str
508
530
  if not workspace:
509
531
  path = "/wms"
@@ -526,6 +548,9 @@ class GeoServerCloud:
526
548
  def get_tile(
527
549
  self, layer, format, tile_matrix_set, tile_matrix, row, column
528
550
  ) -> ResponseWrapper | None:
551
+ """
552
+ WMTS GetTile request
553
+ """
529
554
  if not self.wmts:
530
555
  self.create_wmts()
531
556
  if self.wmts:
@@ -539,18 +564,108 @@ class GeoServerCloud:
539
564
  )
540
565
  return None
541
566
 
567
+ def get_feature(
568
+ self,
569
+ workspace: str,
570
+ type_name: str,
571
+ feature_id: int | None = None,
572
+ max_feature: int | None = None,
573
+ format: str = "application/json",
574
+ ) -> dict[str, Any] | bytes:
575
+ """WFS GetFeature request
576
+ Return the feature(s) as dict if found, otherwise return the raw response content as bytes
577
+ """
578
+ path = f"/{workspace}/wfs"
579
+ params = {
580
+ "service": "WFS",
581
+ "version": "1.1.0",
582
+ "request": "GetFeature",
583
+ "typeName": type_name,
584
+ "outputFormat": format,
585
+ }
586
+ if feature_id:
587
+ params["featureID"] = str(feature_id)
588
+ if max_feature:
589
+ params["maxFeatures"] = str(max_feature)
590
+ response = self.get_request(path, params=params)
591
+ try:
592
+ return response.json()
593
+ except JSONDecodeError:
594
+ return response.content
595
+
596
+ def describe_feature_type(
597
+ self,
598
+ workspace: str,
599
+ type_name: str | None = None,
600
+ format: str = "application/json",
601
+ ) -> dict[str, Any] | bytes:
602
+ """WFS DescribeFeatureType request
603
+ Return the feature type(s) as dict if found, otherwise return the raw response content as bytes
604
+ """
605
+ path = f"/{workspace}/wfs"
606
+ params = {
607
+ "service": "WFS",
608
+ "version": "1.1.0",
609
+ "request": "DescribeFeatureType",
610
+ "outputFormat": format,
611
+ }
612
+ if type_name:
613
+ params["typeName"] = type_name
614
+ response = self.get_request(path, params=params)
615
+ try:
616
+ return response.json()
617
+ except JSONDecodeError:
618
+ return response.content
619
+
620
+ def get_property_value(
621
+ self,
622
+ workspace: str,
623
+ type_name: str,
624
+ property: str,
625
+ ) -> dict | list | bytes:
626
+ """WFS GetPropertyValue request
627
+ Return the properties as dict (if one feature was found), a list (if multiple features were found)
628
+ or an empty dict if no feature was found. Otherwise throw a requests.exceptions.HTTPError
629
+ """
630
+ path = f"/{workspace}/wfs"
631
+ params = {
632
+ "service": "WFS",
633
+ "version": "2.0.0",
634
+ "request": "GetPropertyValue",
635
+ "typeNames": type_name,
636
+ "valueReference": property,
637
+ }
638
+ response = self.get_request(path, params=params)
639
+ value_collection = xmltodict.parse(response.content).get("wfs:ValueCollection")
640
+ if not value_collection:
641
+ return response.content
642
+ else:
643
+ return value_collection.get("wfs:member", {})
644
+
542
645
  def create_role(self, role_name: str) -> Response:
646
+ """
647
+ Create a GeoServer role
648
+ """
543
649
  return self.post_request(f"/rest/security/roles/role/{role_name}")
544
650
 
545
651
  def delete_role(self, role_name: str) -> Response:
652
+ """
653
+ Delete a GeoServer role
654
+ """
546
655
  return self.delete_request(f"/rest/security/roles/role/{role_name}")
547
656
 
548
657
  def create_role_if_not_exists(self, role_name: str) -> Response | None:
658
+ """
659
+ Create a GeoServer role if it does not yet exist
660
+ """
549
661
  if self.role_exists(role_name):
550
662
  return None
551
663
  return self.create_role(role_name)
552
664
 
553
665
  def role_exists(self, role_name: str) -> bool:
666
+ """
667
+ Check if a GeoServer role exists
668
+ """
554
669
  response = self.get_request(
555
670
  f"/rest/security/roles", headers={"Accept": "application/json"}
556
671
  )
@@ -565,6 +680,9 @@ class GeoServerCloud:
565
680
  user: str | None = None,
566
681
  workspace: str | None = None,
567
682
  ) -> Response:
683
+ """
684
+ Create a GeoServer ACL admin rule
685
+ """
568
686
  path = "/acl/api/adminrules"
569
687
  return self.post_request(
570
688
  path,
@@ -578,44 +696,101 @@ class GeoServerCloud:
578
696
  )
579
697
 
580
698
  def delete_acl_admin_rule(self, id: int) -> Response:
699
+ """
700
+ Delete a GeoServer ACL admin rule by id
701
+ """
581
702
  path = f"/acl/api/adminrules/id/{id}"
582
703
  return self.delete_request(path)
583
704
 
584
705
  def delete_all_acl_admin_rules(self) -> Response:
706
+ """
707
+ Delete all existing GeoServer ACL admin rules
708
+ """
585
709
  path = "/acl/api/adminrules"
586
710
  return self.delete_request(path)
587
711
 
712
+ def get_acl_rules(self) -> dict[str, Any]:
713
+ """
714
+ Return all GeoServer ACL data rules
715
+ """
716
+ path = "/acl/api/rules"
717
+ response = self.get_request(path)
718
+ return response.json()
719
+
720
+ def create_acl_rules_for_requests(
721
+ self,
722
+ requests: list[str],
723
+ priority: int = 0,
724
+ access: str = "DENY",
725
+ role: str | None = None,
726
+ service: str | None = None,
727
+ workspace: str | None = None,
728
+ ) -> list[Response]:
729
+ """
730
+ Create ACL rules for multiple type of OGC requests
731
+ """
732
+ responses = []
733
+ for request in requests:
734
+ responses.append(
735
+ self.create_acl_rule(
736
+ priority=priority,
737
+ access=access,
738
+ role=role,
739
+ request=request,
740
+ service=service,
741
+ workspace=workspace,
742
+ )
743
+ )
744
+ return responses
745
+
588
746
  def create_acl_rule(
589
747
  self,
590
748
  priority: int = 0,
591
749
  access: str = "DENY",
592
750
  role: str | None = None,
751
+ user: str | None = None,
593
752
  service: str | None = None,
753
+ request: str | None = None,
594
754
  workspace: str | None = None,
595
755
  ) -> Response:
756
+ """
757
+ Create a GeoServer ACL data rule
758
+ """
596
759
  path = "/acl/api/rules"
597
- return self.post_request(
598
- path,
599
- json={
600
- "priority": priority,
601
- "access": access,
602
- "role": role,
603
- "service": service,
604
- "workspace": workspace,
605
- },
606
- )
760
+ json = {"priority": priority, "access": access}
761
+ if role:
762
+ json["role"] = role
763
+ if user:
764
+ json["user"] = user
765
+ if service:
766
+ json["service"] = service
767
+ if request:
768
+ json["request"] = request
769
+ if workspace:
770
+ json["workspace"] = workspace
771
+ return self.post_request(path, json=json)
607
772
 
608
773
  def delete_all_acl_rules(self) -> Response:
774
+ """
775
+ Delete all existing GeoServer ACL data rules
776
+ """
609
777
  path = "/acl/api/rules"
610
778
  return self.delete_request(path)
611
779
 
612
780
  def create_or_update_resource(self, path, resource_path, payload) -> Response:
781
+ """
782
+ Create a GeoServer resource or update it if it already exists
783
+ """
613
784
  if not self.resource_exists(resource_path):
614
785
  return self.post_request(path, json=payload)
615
786
  else:
616
787
  return self.put_request(resource_path, json=payload)
617
788
 
618
789
  def create_gridset(self, epsg: int) -> Response | None:
790
+ """
791
+ Create a gridset for GeoWebCache for a given projection
792
+ Supported EPSG codes are 2056, 21781 and 3857
793
+ """
619
794
  resource_path: str = f"/gwc/rest/gridsets/EPSG:{epsg}.xml"
620
795
  if self.resource_exists(resource_path):
621
796
  return None
@@ -628,6 +803,9 @@ class GeoServerCloud:
628
803
  return self.put_request(resource_path, data=data, headers=headers)
629
804
 
630
805
  def resource_exists(self, path: str) -> bool:
806
+ """
807
+ Check if a resource (given its path) exists in GeoServer
808
+ """
631
809
  # GeoServer raises a 500 when posting to a datastore or feature type that already exists, so first do
632
810
  # a get request
633
811
  response = self.get_request(path)
@@ -0,0 +1,83 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <gridSet>
3
+ <name>EPSG:21781</name>
4
+ <srs>
5
+ <number>21781</number>
6
+ </srs>
7
+ <extent>
8
+ <coords>
9
+ <double>420000.0</double>
10
+ <double>-674000.0</double>
11
+ <double>1444000.0</double>
12
+ <double>350000.0</double>
13
+ </coords>
14
+ </extent>
15
+ <resolutions>
16
+ <double>4000.0</double>
17
+ <double>3750.0</double>
18
+ <double>3500.0</double>
19
+ <double>3250.0</double>
20
+ <double>3000.0</double>
21
+ <double>2750.0</double>
22
+ <double>2500.0</double>
23
+ <double>2250.0</double>
24
+ <double>2000.0</double>
25
+ <double>1750.0</double>
26
+ <double>1500.0</double>
27
+ <double>1250.0</double>
28
+ <double>1000.0</double>
29
+ <double>750.0</double>
30
+ <double>650.0</double>
31
+ <double>500.0</double>
32
+ <double>250.0</double>
33
+ <double>100.0</double>
34
+ <double>50.0</double>
35
+ <double>20.0</double>
36
+ <double>10.0</double>
37
+ <double>5.0</double>
38
+ <double>2.5</double>
39
+ <double>2.0</double>
40
+ <double>1.5</double>
41
+ <double>1.0</double>
42
+ <double>0.5</double>
43
+ <double>0.25</double>
44
+ <double>0.1</double>
45
+ </resolutions>
46
+ <scaleNames>
47
+ <string>EPSG:21781:0</string>
48
+ <string>EPSG:21781:1</string>
49
+ <string>EPSG:21781:2</string>
50
+ <string>EPSG:21781:3</string>
51
+ <string>EPSG:21781:4</string>
52
+ <string>EPSG:21781:5</string>
53
+ <string>EPSG:21781:6</string>
54
+ <string>EPSG:21781:7</string>
55
+ <string>EPSG:21781:8</string>
56
+ <string>EPSG:21781:9</string>
57
+ <string>EPSG:21781:10</string>
58
+ <string>EPSG:21781:11</string>
59
+ <string>EPSG:21781:12</string>
60
+ <string>EPSG:21781:13</string>
61
+ <string>EPSG:21781:14</string>
62
+ <string>EPSG:21781:15</string>
63
+ <string>EPSG:21781:16</string>
64
+ <string>EPSG:21781:17</string>
65
+ <string>EPSG:21781:18</string>
66
+ <string>EPSG:21781:19</string>
67
+ <string>EPSG:21781:20</string>
68
+ <string>EPSG:21781:21</string>
69
+ <string>EPSG:21781:22</string>
70
+ <string>EPSG:21781:23</string>
71
+ <string>EPSG:21781:24</string>
72
+ <string>EPSG:21781:25</string>
73
+ <string>EPSG:21781:26</string>
74
+ <string>EPSG:21781:27</string>
75
+ <string>EPSG:21781:28</string>
76
+ </scaleNames>
77
+ <tileHeight>256</tileHeight>
78
+ <tileWidth>256</tileWidth>
79
+ <yCoordinateFirst>false</yCoordinateFirst>
80
+ <alignTopLeft>false</alignTopLeft>
81
+ <metersPerUnit>1.0</metersPerUnit>
82
+ <pixelSize>2.8E-4</pixelSize>
83
+ </gridSet>
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "geoservercloud"
3
- version = "0.2.3.dev3"
3
+ version = "0.2.4.dev2"
4
4
  description = "Lightweight Python client to interact with GeoServer Cloud REST API, GeoServer ACL and OGC services"
5
5
  authors = ["Camptocamp <info@camptocamp.com>"]
6
6
  license = "BSD-2-Clause"