Coverage for gws-app/gws/spec/core.py: 100%

192 statements  

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

1"""Core data structures and constants for the specs.""" 

2 

3from typing import TypeAlias, Any 

4import os 

5 

6 

7class Error(Exception): 

8 """Base class for spec errors.""" 

9 

10 pass 

11 

12 

13class GeneratorError(Error): 

14 """Raised when the spec generator fails.""" 

15 

16 pass 

17 

18 

19class ReadError(Error): 

20 """Raised when a value does not match its spec type. 

21 

22 The arguments are the message and the offending value. With verbose errors, 

23 a ``gws.ConfigErrorInfo`` object is added as the third argument. 

24 """ 

25 

26 pass 

27 

28 

29class LoadError(Error): 

30 """Raised when a class cannot be loaded from its module.""" 

31 

32 pass 

33 

34 

35class c: 

36 """Type kinds, the values of ``Type.c``.""" 

37 

38 ATOM = 'ATOM' 

39 """Atomic, one of the built-in types.""" 

40 CLASS = 'CLASS' 

41 """Class, a user-defined type.""" 

42 CALLABLE = 'CALLABLE' 

43 """Callable, a callable argument.""" 

44 CONSTANT = 'CONSTANT' 

45 """Constant type.""" 

46 DICT = 'DICT' 

47 """Generic dictionary type.""" 

48 ENUM = 'ENUM' 

49 """Enum type.""" 

50 EXPR = 'EXPR' 

51 """Compile-time expression type.""" 

52 FUNCTION = 'FUNCTION' 

53 """Function, a callable type.""" 

54 LIST = 'LIST' 

55 """Generic list type.""" 

56 LITERAL = 'LITERAL' 

57 """Literal type.""" 

58 METHOD = 'METHOD' 

59 """Method, a callable type with a specific signature.""" 

60 MODULE = 'MODULE' 

61 """Module type.""" 

62 NONE = 'NONE' 

63 """None type.""" 

64 OPTIONAL = 'OPTIONAL' 

65 """Optional, a type that can be None.""" 

66 PROPERTY = 'PROPERTY' 

67 """Property, a type that is a property of a class.""" 

68 SET = 'SET' 

69 """Generic set type.""" 

70 TUPLE = 'TUPLE' 

71 """Generic tuple type.""" 

72 TYPE = 'TYPE' 

73 """Type alias.""" 

74 UNION = 'UNION' 

75 """Union, a type that can be one of several types.""" 

76 UNDEFINED = 'UNDEFINED' 

77 """Undefined, a type that is not defined.""" 

78 VARIANT = 'VARIANT' 

79 """Variant, a type that can be one of several types with a tag.""" 

80 

81 EXT = 'EXT' 

82 """Extension, a ``gws.ext`` alias.""" 

83 COMMAND = 'COMMAND' 

84 """Command, a method decorated as ``gws.ext.command``.""" 

85 

86 

87TypeKind: TypeAlias = str 

88"""Type kind, one of the constants in ``c``.""" 

89TypeUid: TypeAlias = str 

90"""Type unique identifier, a string that identifies the type.""" 

91 

92 

93class Type: 

94 """Spec type record, describes a single type, property, method or module. 

95 

96 Which fields are populated depends on the type kind ``c``. 

97 """ 

98 

99 c: TypeKind 

100 """Type kind, one of the constants in ``c``.""" 

101 uid: TypeUid 

102 """Type unique identifier, a string that identifies the type.""" 

103 

104 extName: str = '' 

105 """``gws.ext`` name of an extension type, variant or command method, if any.""" 

106 

107 title: str = '' 

108 """Documentation title, filled from the strings by ``get_config_types``.""" 

109 doc: str = '' 

110 """Documentation string for the type.""" 

111 ident: str = '' 

112 """Source code identifier (class, property or method name), used in the documentation.""" 

113 name: str = '' 

114 """Qualified name of a named type, e.g. ``gws.base.layer.core.Config``.""" 

115 pos: str = '' 

116 """Source code position of the definition, as ``path:line``.""" 

117 

118 modName: str = '' 

119 """Name of the module that defines this type.""" 

120 modPath: str = '' 

121 """Path to the module that defines this type.""" 

122 

123 tArg: TypeUid = '' 

124 """For ``METHOD`` types, type uid of the last (request) argument.""" 

125 tItem: TypeUid = '' 

126 """For ``LIST`` and ``SET`` types, type uid of the item.""" 

127 tKey: TypeUid = '' 

128 """For ``DICT`` types, type uid of the key.""" 

129 tModule: TypeUid = '' 

130 """Type uid of the type's module.""" 

131 tOwner: TypeUid = '' 

132 """For ``PROPERTY`` and ``METHOD`` types, type uid of the owning class.""" 

133 tReturn: TypeUid = '' 

134 """For ``METHOD`` types, type uid of the return value.""" 

135 tTarget: TypeUid = '' 

136 """For ``TYPE``, ``EXT`` and ``OPTIONAL`` types, type uid of the target type.""" 

137 tValue: TypeUid = '' 

138 """For ``PROPERTY`` types, type uid of the value; for ``DICT`` types, type uid of the dict values.""" 

139 

140 tArgs: list[TypeUid] = [] 

141 """For ``METHOD`` types, type uids of the arguments.""" 

142 tItems: list[TypeUid] = [] 

143 """For ``UNION``, ``TUPLE`` and ``CALLABLE`` types, type uids of the items.""" 

144 tSupers: list[TypeUid] = [] 

145 """For ``CLASS`` types, type uids of the base classes.""" 

146 tMembers: dict[str, TypeUid] = {} 

147 """For ``VARIANT`` types, member type uids keyed by the ``type`` tag.""" 

148 tProperties: dict[str, TypeUid] = {} 

149 """For ``CLASS`` types, property type uids keyed by property name, including inherited properties.""" 

150 

151 defaultValue: Any = None 

152 """Default value for a property.""" 

153 defaultExpression: Any = None 

154 """Unevaluated default of a property (a constant or enum reference), evaluated by the normalizer.""" 

155 hasDefault: bool = False 

156 """True if the type has a default value.""" 

157 constValue: Any = None 

158 """Constant value for a constant type.""" 

159 

160 enumDocs: dict = {} 

161 """For ``ENUM`` types, member docstrings keyed by member name.""" 

162 enumValues: dict = {} 

163 """For ``ENUM`` types, member values keyed by member name.""" 

164 

165 literalValues: list = [] 

166 """For ``LITERAL`` types, the allowed values.""" 

167 

168 isConfig: bool = False 

169 """True if the type is reachable from the application ``Config``.""" 

170 

171 

172def make_type(args: dict): 

173 """Create a ``Type`` object. 

174 

175 Args: 

176 args: Attribute values for the type. 

177 

178 Returns: 

179 A new ``Type`` object. 

180 """ 

181 

182 typ = Type() 

183 vars(typ).update(args) 

184 return typ 

185 

186 

187class Chunk: 

188 """Source code chunk, the core packages or a plugin with their source files.""" 

189 

190 name: str 

191 """Name of the chunk.""" 

192 sourceDir: str 

193 """Source directory of the chunk.""" 

194 bundleDir: str 

195 """Directory where the client bundle of the chunk is stored.""" 

196 paths: dict[str, list[str]] 

197 """Source file paths, grouped by file kind (``python``, ``ts``, ``css``, ``theme``, ``strings``).""" 

198 exclude: list[str] 

199 """Path fragments to exclude from the chunk.""" 

200 

201 

202class SpecData: 

203 """Specs data, produced by the generator and loaded by the runtime.""" 

204 

205 meta: dict 

206 """Build-time metadata: ``version``, ``manifestPath`` and ``manifest``.""" 

207 chunks: list[Chunk] 

208 """Source code chunks of the application and its plugins.""" 

209 serverTypes: list[Type] 

210 """Types used by the server: configuration, request and response types, ext objects and command methods.""" 

211 strings: dict[str, dict[str, str]] 

212 """Documentation strings keyed by language code and type uid.""" 

213 

214 

215class v: 

216 """Constants for the spec generator and runtime.""" 

217 

218 APP_NAME = 'gws' 

219 """Application package name.""" 

220 EXT_PREFIX = APP_NAME + '.ext' 

221 """Prefix of all extension names.""" 

222 EXT_DECL_PREFIX = EXT_PREFIX + '.new.' 

223 """Prefix of ``gws.ext.new`` declarations in the sources.""" 

224 EXT_CONFIG_PREFIX = EXT_PREFIX + '.config.' 

225 """Prefix of extension config names.""" 

226 EXT_PROPS_PREFIX = EXT_PREFIX + '.props.' 

227 """Prefix of extension props names.""" 

228 EXT_OBJECT_PREFIX = EXT_PREFIX + '.object.' 

229 """Prefix of extension object names.""" 

230 EXT_COMMAND_PREFIX = EXT_PREFIX + '.command.' 

231 """Prefix of command names.""" 

232 

233 EXT_COMMAND_API_PREFIX = EXT_COMMAND_PREFIX + 'api.' 

234 """Prefix of API command names.""" 

235 EXT_COMMAND_GET_PREFIX = EXT_COMMAND_PREFIX + 'get.' 

236 """Prefix of web GET command names.""" 

237 EXT_COMMAND_CLI_PREFIX = EXT_COMMAND_PREFIX + 'cli.' 

238 """Prefix of CLI command names.""" 

239 

240 EXT_OBJECT_CLASS = 'Object' 

241 """Default object class name in a ``gws.ext.new`` declaration.""" 

242 EXT_CONFIG_CLASS = 'Config' 

243 """Default config class name in a ``gws.ext.new`` declaration.""" 

244 EXT_PROPS_CLASS = 'Props' 

245 """Default props class name in a ``gws.ext.new`` declaration.""" 

246 

247 CLIENT_NAME = 'gc' 

248 """Name of the client chunk.""" 

249 VARIANT_TAG = 'type' 

250 """Name of the property that selects the member of a variant.""" 

251 DEFAULT_VARIANT_TAG = 'default' 

252 """Variant member used when the tag property is missing.""" 

253 

254 ATOMS = ['any', 'bool', 'bytes', 'float', 'int', 'str'] 

255 """Names of atomic types.""" 

256 

257 BUILTINS = ATOMS + ['type', 'object', 'Exception', 'dict', 'list', 'set', 'tuple'] 

258 """Built-in names, registered as ``ATOM`` types.""" 

259 

260 BUILTIN_TYPES = [ 

261 'Any', 

262 'Callable', 

263 'ContextManager', 

264 'Dict', 

265 'Enum', 

266 'Iterable', 

267 'Iterator', 

268 'List', 

269 'Literal', 

270 'Optional', 

271 'Protocol', 

272 'Set', 

273 'Tuple', 

274 'TypeAlias', 

275 'Union', 

276 # imported in TYPE_CHECKING 

277 'datetime.datetime', 

278 'osgeo', 

279 'sqlalchemy', 

280 # vendor libs 

281 'gws.lib.vendor', 

282 'gws.lib.sa', 

283 ] 

284 """Names from ``typing`` and foreign modules that are treated as built-in.""" 

285 

286 # those star-imported in gws/__init__.py 

287 GLOBAL_MODULES = [ 

288 APP_NAME + '.core.const', 

289 APP_NAME + '.core.util', 

290 ] 

291 """Modules whose names are available directly as ``gws.<Name>``.""" 

292 

293 DEFAULT_EXT_SUPERS = { 

294 'config': APP_NAME + '.core.types.ConfigWithAccess', 

295 'props': APP_NAME + '.core.types.Props', 

296 } 

297 """Default base classes for synthesized ext config and props classes.""" 

298 

299 # prefix for gws.plugin class names 

300 PLUGIN_PREFIX = APP_NAME + '.plugin' 

301 

302 # inline comment symbol 

303 INLINE_COMMENT_SYMBOL = '#:' 

304 

305 # where we are 

306 SELF_DIR = os.path.dirname(__file__) 

307 

308 # path to `/repository-root/app` 

309 APP_DIR = os.path.abspath(SELF_DIR + '/../..') 

310 

311 EXCLUDE_PATHS = ['___', '/vendor/', 'test', 'core/ext', '__pycache__'] 

312 """Path fragments of source files the generator skips.""" 

313 

314 FILE_KINDS = [ 

315 ['.py', 'python'], 

316 ['/index.ts', 'ts'], 

317 ['/index.tsx', 'ts'], 

318 ['/index.css.js', 'css'], 

319 ['.theme.css.js', 'theme'], 

320 ['/strings.ini', 'strings'], 

321 ] 

322 """Source file suffixes and the file kinds they map to.""" 

323 

324 PLUGIN_DIR = '/gws/plugin' 

325 """Directory of the built-in plugins, relative to the app directory.""" 

326 

327 SYSTEM_CHUNKS = [ 

328 [CLIENT_NAME, f'/js/src/{CLIENT_NAME}'], 

329 [f'{APP_NAME}.core', '/gws/core'], 

330 [f'{APP_NAME}.base', '/gws/base'], 

331 [f'{APP_NAME}.gis', '/gws/gis'], 

332 [f'{APP_NAME}.lib', '/gws/lib'], 

333 [f'{APP_NAME}.server', '/gws/server'], 

334 [f'{APP_NAME}.helper', '/gws/helper'], 

335 ] 

336 """Names and source directories of the system chunks."""