Coverage for gws-app/gws/base/application/core.py: 91%

147 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Application object and root configuration.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.base.action 

7import gws.base.application.middleware 

8import gws.base.auth 

9import gws.base.client 

10import gws.base.database 

11import gws.base.exporter 

12import gws.base.job 

13import gws.base.metadata 

14import gws.base.model 

15import gws.base.printer 

16import gws.base.search 

17import gws.base.storage 

18import gws.base.template 

19import gws.base.web 

20import gws.config 

21import gws.gis.cache 

22import gws.lib.font 

23import gws.server.manager 

24import gws.server.monitor 

25 

26DEFAULT_LOCALE = ['en_CA'] 

27 

28DEFAULT_TEMPLATE_SUBJECTS = [ 

29 'application.home', 

30 'application.error', 

31 'project.home', 

32 'project.description', 

33 'layer.description', 

34 'feature.description', 

35 'feature.title', 

36 'feature.label', 

37] 

38 

39DEFAULT_TEMPLATES = [ 

40 gws.Config( 

41 type='html', 

42 path=f'{gws.c.APP_DIR}/gws/base/application/templates/{_s.replace(".", "_")}.cx.html', 

43 subject=_s, 

44 access=gws.c.PUBLIC, 

45 uid=f'gws.base.application.default_template.{_s}', 

46 ) 

47 for _s in DEFAULT_TEMPLATE_SUBJECTS 

48] 

49 

50DEFAULT_PRINTER = gws.Config( 

51 uid='gws.base.application.default_printer', 

52 access=gws.c.PUBLIC, 

53 template=gws.Config( 

54 type='html', 

55 path=f'{gws.c.APP_DIR}/gws/base/application/templates/project_print.cx.html', 

56 mapSize=(200, 180, gws.Uom.mm), 

57 ), 

58 qualityLevels=[gws.TemplateQualityLevel(dpi=72)], 

59) 

60 

61DEFAULT_CSS_FILENAME = 'style.css' 

62 

63class Config(gws.ConfigWithAccess): 

64 """Root configuration of the application.""" 

65 

66 actions: Optional[list[gws.ext.config.action]] 

67 """Server actions available in all projects.""" 

68 auth: Optional[gws.base.auth.manager.Config] 

69 """Authentication methods, providers and session options.""" 

70 cache: Optional[gws.gis.cache.GlobalConfig] 

71 """Options for tile cache seeding.""" 

72 client: Optional[gws.base.client.Config] 

73 """Default client UI elements and options for all projects.""" 

74 database: Optional[gws.base.database.manager.Config] 

75 """Database connections.""" 

76 developer: Optional[dict] 

77 """Options for development and debugging, not for production use.""" 

78 exporters: Optional[list[gws.ext.config.exporter]] 

79 """Formats for exporting features.""" 

80 finders: Optional[list[gws.ext.config.finder]] 

81 """Search providers available in all projects.""" 

82 fonts: Optional[gws.lib.font.Config] 

83 """Directory with additional fonts for rendering and printing.""" 

84 helpers: Optional[list[gws.ext.config.helper]] 

85 """Auxiliary objects, used throughout the application.""" 

86 locales: Optional[list[gws.LocaleUid]] 

87 """Default locales for all projects.""" 

88 metadata: Optional[gws.base.metadata.Config] 

89 """Application metadata.""" 

90 models: Optional[list[gws.ext.config.model]] 

91 """Data models available in all projects.""" 

92 owsServices: Optional[list[gws.ext.config.owsService]] 

93 """OWS services provided by the server.""" 

94 projectDirs: Optional[list[gws.DirPath]] 

95 """Directories searched recursively for project configuration files.""" 

96 projectPaths: Optional[list[gws.FilePath]] 

97 """Additional project configuration files.""" 

98 printers: Optional[list[gws.ext.config.printer]] 

99 """Configuration for print templates and quality levels.""" 

100 projects: Optional[list[gws.ext.config.project]] 

101 """Projects, each with its own map, client and actions.""" 

102 server: Optional[gws.server.Config] 

103 """Server processes, logging and monitoring options.""" 

104 storage: Optional[gws.base.storage.manager.Config] 

105 """Storage providers for data that users save in the client.""" 

106 templates: Optional[list[gws.ext.config.template]] 

107 """Templates available in all projects.""" 

108 templateOptions: Optional[gws.TemplateOptions] 

109 """Login form, footer and extra resources of the built-in home pages.""" 

110 title: Optional[str] 

111 """Application title.""" 

112 vars: Optional[dict] 

113 """Custom variables.""" 

114 web: Optional[gws.base.web.manager.Config] 

115 """Web site and SSL options.""" 

116 lastUid: int = 1 

117 """Last used UID for auto-generated UIDs.""" 

118 

119 

120class Object(gws.Application): 

121 """Application object.""" 

122 

123 _helperMap: dict[str, gws.Node] 

124 

125 _developerOptions: dict 

126 

127 _supportedCrs: list[gws.Crs] 

128 _registeredCrs: set[gws.Crs] 

129 

130 def configure(self): 

131 self._registeredCrs = set() 

132 

133 self.version = self.root.specs.version 

134 self.versionString = f'GWS version {self.version}' 

135 

136 if gws.u.is_file('/GWS_REVISION'): 

137 self.versionString += ' (rev. ' + gws.u.read_file('/GWS_REVISION') + ')' 

138 

139 if not gws.env.GWS_IN_TEST: 

140 gws.log.info('*' * 80) 

141 gws.log.info(self.versionString) 

142 gws.log.info('*' * 80) 

143 

144 self.vars = self.cfg('vars') or {} 

145 

146 self.title = self.cfg('title') or '' 

147 

148 self.serverMgr = self.create_child(gws.server.manager.Object, self.cfg('server')) 

149 # NB need defaults from the server 

150 self.config.server = self.serverMgr.config 

151 

152 gws.log.set_level(self.cfg('server.log.level')) 

153 

154 self._developerOptions = self.cfg('developer') or {} 

155 if self._developerOptions: 

156 gws.log.warning(f'developer mode enabled: {self._developerOptions}') 

157 

158 self.monitor = self.create_child(gws.server.monitor.Object, self.serverMgr.cfg('monitor')) 

159 

160 self.localeUids = self.cfg('locales') or DEFAULT_LOCALE 

161 self.metadata = gws.base.metadata.from_config(self.cfg('metadata')) 

162 

163 self.middlewareMgr = self.create_child(gws.base.application.middleware.Object) 

164 

165 p = self.cfg('fonts') 

166 if p: 

167 gws.lib.font.configure(p) 

168 

169 # NB the order of initialization is important 

170 # - db 

171 # - helpers 

172 # - auth providers 

173 # - actions, client, web 

174 # - finally, projects 

175 

176 self.databaseMgr = self.create_child(gws.base.database.manager.Object, self.cfg('database')) 

177 

178 helpers = self.create_children(gws.ext.object.helper, self.cfg('helpers')) 

179 self._helperMap = {p.extType: p for p in helpers} 

180 

181 self.storageMgr = self.create_child(gws.base.storage.manager.Object, self.cfg('storage')) 

182 self.authMgr = self.create_child(gws.base.auth.manager.Object, self.cfg('auth')) 

183 

184 # @TODO default API 

185 self.actionMgr = self.create_child(gws.base.action.manager.Object) 

186 self.actions = self.create_children(gws.ext.object.action, self.cfg('actions')) 

187 

188 self.webMgr = self.create_child(gws.base.web.manager.Object, self.cfg('web')) 

189 

190 self.searchMgr = self.create_child(gws.base.search.manager.Object) 

191 self.finders = self.create_children(gws.ext.object.finder, self.cfg('finders')) 

192 

193 self.modelMgr = self.create_child(gws.base.model.manager.Object) 

194 self.models = self.create_children(gws.ext.object.model, self.cfg('models')) 

195 

196 self.templateMgr = self.create_child(gws.base.template.manager.Object) 

197 self.templates = self.create_children(gws.ext.object.template, self.cfg('templates')) 

198 for cfg in DEFAULT_TEMPLATES: 

199 self.templates.append(self.root.create_shared(gws.ext.object.template, cfg)) 

200 

201 p = self.cfg('templateOptions') 

202 defaults = gws.TemplateOptions( 

203 withLogin = any(a.extType == 'auth' for a in self.actions), 

204 homeResources = [], 

205 projectResources = [], 

206 ) 

207 if gws.u.is_file(self.webMgr.site.staticRoot.dir + f'/{DEFAULT_CSS_FILENAME}'): 

208 defaults.homeResources = [f'/{DEFAULT_CSS_FILENAME}'] 

209 defaults.projectResources = [f'/{DEFAULT_CSS_FILENAME}'] 

210 

211 self.templateOptions = gws.u.merge(defaults, self.cfg('templateOptions')) 

212 

213 self.exporterMgr = self.create_child(gws.base.exporter.manager.Object) 

214 self.exporters = self.create_children(gws.ext.object.exporter, self.cfg('exporters')) 

215 

216 self.jobMgr = self.create_child(gws.base.job.manager.Object) 

217 

218 self.printerMgr = self.create_child(gws.base.printer.manager.Object) 

219 self.printers = self.create_children(gws.ext.object.printer, self.cfg('printers')) 

220 self.defaultPrinter = self.root.create_shared(gws.ext.object.printer, DEFAULT_PRINTER) 

221 

222 self.owsServices = self.create_children(gws.ext.object.owsService, self.cfg('owsServices')) 

223 

224 self.client = self.create_child(gws.base.client.Object, self.cfg('client')) 

225 

226 self.projects = self.create_children(gws.ext.object.project, self.cfg('projects')) 

227 

228 def activate(self): 

229 gws.log.set_level(self.cfg('server.log.level')) 

230 

231 for p in self.root.configPaths: 

232 if p.startswith(gws.c.CONFIG_DIR): 

233 # do not watch derived config paths 

234 continue 

235 self.monitor.watch_file(p) 

236 for d in self.config.get('projectDirs') or []: 

237 self.monitor.watch_directory(d, gws.config.CONFIG_PATH_PATTERN) 

238 

239 def project(self, uid): 

240 for p in self.projects: 

241 if p.uid == uid: 

242 return p 

243 

244 def register_supported_crs(self, crs): 

245 self._registeredCrs.add(crs) 

246 if hasattr(self, '_supportedCrs'): 

247 del self._supportedCrs 

248 

249 def supported_crs(self): 

250 if not hasattr(self, '_supportedCrs'): 

251 crs_set = set(self._registeredCrs) 

252 for m in self.root.find_all(gws.ext.object.map): 

253 crs_set.add(m.bounds.crs) 

254 for s in self.root.find_all(gws.ext.object.owsService): 

255 for b in s.supportedBounds: 

256 crs_set.add(b.crs) 

257 self._supportedCrs = sorted(crs_set, key=lambda c: c.srid) 

258 return self._supportedCrs 

259 

260 def helper(self, ext_type): 

261 if ext_type not in self._helperMap: 

262 p = self.create_child(gws.ext.object.helper, type=ext_type) 

263 if not p: 

264 raise gws.Error(f'helper {ext_type!r} not found') 

265 gws.log.info(f'created helper {ext_type!r}') 

266 self._helperMap[ext_type] = p 

267 return self._helperMap.get(ext_type) 

268 

269 def developer_option(self, key): 

270 return self._developerOptions.get(key)