Coverage for gws-app/gws/base/ows/client/provider.py: 0%
110 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1from typing import Optional
2import base64
4import gws
5import gws.gis.crs
6import gws.gis.source
7import gws.lib.net
8import gws.lib.mime
10from . import request
12_IMAGE_VERBS = {
13 gws.OwsVerb.GetLegendGraphic,
14 gws.OwsVerb.GetMap,
15 gws.OwsVerb.GetTile,
16}
18_PREFER_IMAGE_MIME = [gws.lib.mime.PNG, gws.lib.mime.JPEG]
19_PREFER_XML_MIME = [gws.lib.mime.GML3, gws.lib.mime.GML, gws.lib.mime.XML]
22class OperationConfig(gws.Config):
23 """Custom OWS operation."""
24 verb: gws.OwsVerb
25 """OWS verb."""
26 formats: Optional[list[str]]
27 """Supported formats."""
28 params: Optional[dict]
29 """Operation parameters. (added in 8.1)"""
30 postUrl: Optional[gws.Url]
31 """URL for POST requests."""
32 url: Optional[gws.Url]
33 """URL for GET requests."""
36class AuthorizationConfig(gws.Config):
37 """Service authorization. (added in 8.1)"""
38 type: str
39 """Authorization type (only "basic" is supported)."""
40 username: str = ''
41 """User name."""
42 password: str = ''
43 """Password."""
46class Config(gws.Config):
47 """OWS provider configuration."""
49 capsCacheMaxAge: gws.Duration = '1d'
50 """Max cache age for capabilities documents."""
51 forceCrs: Optional[gws.CrsName]
52 """Use this CRS for requests."""
53 alwaysXY: bool = False
54 """Force XY orientation for lat/lon projections."""
55 maxRequests: int = 0
56 """Max concurrent requests to this source."""
57 operations: Optional[list[OperationConfig]]
58 """Override operations reported in capabilities."""
59 authorization: Optional[AuthorizationConfig]
60 """Service authorization. (added in 8.1)"""
61 url: gws.Url
62 """Service url."""
65class Object(gws.OwsProvider):
66 def configure(self):
67 self.alwaysXY = self.cfg('alwaysXY', default=False)
68 self.forceCrs = gws.gis.crs.get(self.cfg('forceCrs'))
69 self.maxRequests = self.cfg('maxRequests')
70 self.operations = []
71 self.sourceLayers = []
72 self.url = self.cfg('url')
73 self.version = ''
75 p = self.cfg('authorization')
76 self.authorization = gws.OwsAuthorization(p) if p else None
78 def configure_operations(self, operations_from_caps):
79 d = {}
81 for op in operations_from_caps:
82 d[op.verb] = op
84 for cfg in (self.cfg('operations') or []):
85 # add an operation from the config, borrowing missing attributes from a caps op
86 verb = cfg.get('verb')
87 caps_op = d.get(verb, {})
88 d[verb] = gws.OwsOperation(
89 verb=verb,
90 formats=gws.u.first_not_none(cfg.get('formats'), caps_op.get('formats'), []),
91 params=gws.u.first_not_none(cfg.get('params'), caps_op.get('params'), {}),
92 postUrl=gws.u.first_not_none(cfg.get('postUrl'), caps_op.get('postUrl'), ''),
93 url=gws.u.first_not_none(cfg.get('url'), caps_op.get('url'), '')
94 )
96 self.operations = list(d.values())
98 for op in self.operations:
99 op.preferredFormat = self._preferred_format(op)
101 def _preferred_format(self, op: gws.OwsOperation) -> Optional[str]:
102 prefer_fmts = _PREFER_IMAGE_MIME if op.verb in _IMAGE_VERBS else _PREFER_XML_MIME
104 if not op.formats:
105 return prefer_fmts[0]
107 # select the "best" (leftmost in the preferred list) format
109 best_pos = 999
110 best_fmt = ''
112 for fmt in op.formats:
113 canon_fmt = gws.lib.mime.get(fmt)
114 if not canon_fmt or canon_fmt not in prefer_fmts:
115 continue
116 pos = prefer_fmts.index(canon_fmt)
117 if pos < best_pos:
118 best_pos = pos
119 best_fmt = fmt
121 return best_fmt or op.formats[0]
123 def get_operation(self, verb, method=None):
124 for op in self.operations:
125 if op.verb == verb:
126 url = op.postUrl if method == gws.RequestMethod.POST else op.url
127 if url:
128 return op
130 def prepare_operation(self, op: gws.OwsOperation, method: gws.RequestMethod = None, params=None) -> request.Args:
131 args = request.Args(
132 method=method or gws.RequestMethod.GET,
133 headers={},
134 params={},
135 protocol=self.protocol,
136 verb=op.verb,
137 version=self.version,
138 )
140 if args.method == gws.RequestMethod.GET:
141 args.params.update(op.params)
143 args.url = op.url
144 if args.method == gws.RequestMethod.POST:
145 args.url = op.postUrl
147 allowed = op.allowedParameters or {}
149 if params:
150 for name, val in params.items():
151 name = name.upper()
152 if name in allowed and val not in allowed[name]:
153 raise gws.Error(f'invalid parameter value {val!r} for {name!r}')
154 args.params[name] = val
156 if self.authorization and self.authorization.type == 'basic':
157 b = base64.encodebytes(
158 gws.u.to_bytes(self.authorization.username) + b':' + gws.u.to_bytes(self.authorization.password))
159 args.headers['Authorization'] = 'Basic ' + gws.u.to_str(b).strip()
161 return args
163 def get_capabilities(self):
164 url, params = gws.lib.net.extract_params(self.url)
165 op = gws.OwsOperation(
166 formats=[gws.lib.mime.XML],
167 url=url,
168 params=params,
169 verb=gws.OwsVerb.GetCapabilities,
170 )
171 args = self.prepare_operation(op)
172 return request.get_text(args, max_age=self.cfg('capsCacheMaxAge'))