csspin 2.0.0__py3-none-any.whl

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.
csspin/schema.py ADDED
@@ -0,0 +1,204 @@
1
+ # -*- mode: python; coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020 CONTACT Software GmbH
4
+ # https://www.contact-software.com/
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ # Disabling the mypy override rule, since it's suggestions are not that useful
19
+ # for this module.
20
+ # mypy: disable-error-code=override
21
+
22
+ """
23
+ Module defining classes for handling schemas and descriptors for types of data
24
+ used within the package. Schemas describe the structure and properties of data
25
+ objects, while descriptors define how to coerce values and retrieve default
26
+ values for specific data types.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from os.path import normpath
32
+ from typing import TYPE_CHECKING
33
+
34
+ from path import Path
35
+
36
+ from csspin import die, tree
37
+
38
+ if TYPE_CHECKING:
39
+ from typing import Any, Callable, Iterable, Type
40
+
41
+
42
+ class SchemaError(TypeError):
43
+ pass
44
+
45
+
46
+ class BaseDescriptor:
47
+ """
48
+ Base class for descriptors providing methods for coercion and getting
49
+ default values.
50
+ """
51
+
52
+ def __init__(self: BaseDescriptor, description: dict | tree.ConfigTree) -> None:
53
+ self._keyinfo = None
54
+ self.type: list = []
55
+ for key, value in description.items():
56
+ setattr(self, key, value)
57
+ if key == "default":
58
+ self._keyinfo = tree.tree_keyinfo(description, key) # type: ignore[arg-type]
59
+
60
+ def coerce(self: BaseDescriptor, value: Any) -> Any:
61
+ return value
62
+
63
+ def get_default(self: BaseDescriptor, defaultdefault: Any = None) -> Any:
64
+ val = getattr(self, "default", defaultdefault)
65
+ if val is not None:
66
+ val = self.coerce(val)
67
+ return val
68
+
69
+
70
+ DESCRIPTOR_REGISTRY = {}
71
+
72
+
73
+ def descriptor(tag: str) -> Callable:
74
+ def decorator(cls: Type[BaseDescriptor]) -> None:
75
+ DESCRIPTOR_REGISTRY[tag] = cls
76
+
77
+ return decorator
78
+
79
+
80
+ @descriptor("path")
81
+ class PathDescriptor(BaseDescriptor):
82
+ """Descriptor for handling file paths, coercing values to Path objects."""
83
+
84
+ def coerce(
85
+ self: PathDescriptor, value: str | Callable | None
86
+ ) -> str | Path | Callable | None:
87
+ if value not in (None, "") and not callable(value):
88
+ return Path(normpath(value)) # type: ignore[type-var]
89
+ return value
90
+
91
+
92
+ @descriptor("str")
93
+ class StringDescriptor(BaseDescriptor):
94
+ """Descriptor for handling string values."""
95
+
96
+ def coerce(self: StringDescriptor, value: str | Callable) -> str | Callable:
97
+ return str(value) if not callable(value) else value
98
+
99
+
100
+ @descriptor("secret")
101
+ class SecretDescriptor(BaseDescriptor):
102
+ """Descriptor for handling string values."""
103
+
104
+ def coerce(self: SecretDescriptor, value: str | Callable) -> str | Callable:
105
+ return str(value) if not callable(value) else value
106
+
107
+
108
+ @descriptor("int")
109
+ class IntDescriptor(BaseDescriptor):
110
+ """Descriptor for handling integer values."""
111
+
112
+ def coerce(self: IntDescriptor, value: int | Callable) -> int | Callable:
113
+ return int(value) if not callable(value) else value
114
+
115
+
116
+ @descriptor("float")
117
+ class FloatDescriptor(BaseDescriptor):
118
+ """Descriptor for handling float values."""
119
+
120
+ def coerce(self: FloatDescriptor, value: float | Callable) -> float | Callable:
121
+ return float(value) if not callable(value) else value
122
+
123
+
124
+ @descriptor("bool")
125
+ class BoolDescriptor(BaseDescriptor):
126
+ """Descriptor for handling boolean values."""
127
+
128
+ def coerce(self: BoolDescriptor, value: bool | str | Callable) -> bool | Callable:
129
+ return bool(value) if not callable(value) else value
130
+
131
+ def get_default(self: BoolDescriptor) -> bool: # pylint: disable=arguments-differ
132
+ return super().get_default(False) # type: ignore[no-any-return]
133
+
134
+
135
+ @descriptor("list")
136
+ class ListDescriptor(BaseDescriptor):
137
+ """
138
+ Descriptor for handling lists, splitting string values and coercing them to
139
+ lists.
140
+ """
141
+
142
+ def coerce(self: ListDescriptor, value: Iterable | Callable) -> list | Callable:
143
+ if isinstance(value, str):
144
+ return list(value.split())
145
+ if callable(value):
146
+ return value
147
+ return list(value)
148
+
149
+ def get_default(self: ListDescriptor) -> list: # pylint: disable=arguments-differ
150
+ return super().get_default([]) # type: ignore[no-any-return]
151
+
152
+
153
+ @descriptor("object")
154
+ class ObjectDescriptor(BaseDescriptor):
155
+ """
156
+ Descriptor for handling nested objects, recursively building descriptors for
157
+ properties.
158
+ """
159
+
160
+ def __init__(self: ObjectDescriptor, description: dict) -> None:
161
+ super().__init__(description)
162
+ if not hasattr(self, "properties"):
163
+ self.properties = tree.ConfigTree()
164
+ for key, value in self.properties.items():
165
+ ki = tree.tree_keyinfo(self.properties, key)
166
+ odesc = build_descriptor(value)
167
+ self.properties[key] = odesc
168
+ if odesc._keyinfo is None:
169
+ odesc._keyinfo = ki
170
+
171
+ def get_default( # pylint: disable=arguments-differ
172
+ self: ObjectDescriptor,
173
+ ) -> tree.ConfigTree:
174
+ data = super().get_default(tree.ConfigTree())
175
+ for key, desc in self.properties.items():
176
+ data[key] = desc.get_default()
177
+ # pylint: disable=protected-access
178
+ if desc._keyinfo:
179
+ tree.tree_set_keyinfo(data, key, desc._keyinfo)
180
+ data._ConfigTree__schema = self # pylint: disable=protected-access
181
+ return data # type: ignore[no-any-return]
182
+
183
+ def coerce(self: ObjectDescriptor, value: dict) -> dict:
184
+ if not isinstance(value, dict):
185
+ raise SchemaError("dictionary required")
186
+ return value
187
+
188
+
189
+ def build_descriptor(description: dict) -> Type[BaseDescriptor]:
190
+ description["type"] = description.get("type", "object").split()
191
+ factory = DESCRIPTOR_REGISTRY.get(description["type"][0])
192
+ if factory is None:
193
+ die(f"Unknown type '{description['type'][0]}' found in schema configuration.")
194
+ return factory(description) # type: ignore[return-value,misc]
195
+
196
+
197
+ def schema_load(fn: str) -> Type[BaseDescriptor]:
198
+ props = tree.tree_load(fn)
199
+ return build_schema(props)
200
+
201
+
202
+ def build_schema(props: tree.ConfigTree) -> Type[BaseDescriptor]:
203
+ desc = {"type": "object", "properties": props}
204
+ return build_descriptor(desc)
csspin/schema.yaml ADDED
@@ -0,0 +1,134 @@
1
+ # -*- mode: yaml; coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020 CONTACT Software GmbH
4
+ # https://www.contact-software.com/
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ # Schema definitions for spin
19
+
20
+ spin:
21
+ type: object
22
+ help: |
23
+ Settings required for running spin. Many of these cannot be set
24
+ via a spinfile or via the command line, but are computed by spin.
25
+ properties:
26
+ spinfile:
27
+ type: path
28
+ help: |
29
+ The name of the configuration file for the project. This can
30
+ be overridden via 'spin -f <filename>'.
31
+ default: spinfile.yaml
32
+ project_root:
33
+ type: path internal
34
+ help: The path to the project directory.
35
+ # value is set during runtime
36
+ spin_dir:
37
+ type: path internal
38
+ help: |
39
+ The absolute path to spin's project related data. This is also the place
40
+ environments are provisioned.
41
+ default: "{spin.project_root}/.spin"
42
+ launch_dir:
43
+ type: path internal
44
+ help: The directory in which spin was called.
45
+ # value is set during runtime
46
+ data:
47
+ type: path
48
+ default: "{SPIN_DATA}"
49
+ help: Path that points to spins data
50
+ config:
51
+ type: path internal
52
+ default: "{SPIN_CONFIG}"
53
+ help: Path where to store global configuration for spin
54
+ version:
55
+ type: str internal
56
+ help: The version of spin that is being used.
57
+ project_name:
58
+ type: str
59
+ help: The project that is spin running on
60
+ topo_plugins:
61
+ type: list internal
62
+ help: Topological ordered list of initialized plugins
63
+ index_url:
64
+ type: str
65
+ help: The default Python package index url.
66
+ default: https://pypi.org/simple
67
+ extra_index:
68
+ type: str
69
+ help: Additional index to install plugin-packages from.
70
+ hooks:
71
+ type: object internal
72
+ help: |
73
+ A dictionary mapping workflow names to lists of commands. This is
74
+ automatically set up by the `when` argument to :py:func:`spin.task`.
75
+
76
+ environment:
77
+ type: object
78
+ help: |
79
+ `environment` maps additional environment variables and their values which
80
+ will added to the environment when running spin.
81
+
82
+ extra_tasks:
83
+ type: object
84
+ help: |
85
+ `extra_tasks` maps task names to task definitions, where task
86
+ definitions support ``env`` and ``script`` keys.
87
+
88
+ verbosity:
89
+ type: str internal
90
+ default: NORMAL
91
+ help: |
92
+ `verbosity` defines the level of which spin will print messages to stdout.
93
+ Levels are: QUIET, NORMAL, INFO, DEBUG
94
+
95
+ plugins:
96
+ type: list
97
+ help: The list of plugins to import.
98
+
99
+ plugin_packages:
100
+ type: list
101
+ help: |
102
+ A list of plugin packages to install. Supports the same
103
+ requirements specifiers as pip, including URLs, local file names
104
+ and PEP 440 specifiers.
105
+
106
+ plugin_paths:
107
+ type: list
108
+ help: |
109
+ A list of paths to local directories containing plugin modules and their
110
+ optional schemas.
111
+
112
+ platform:
113
+ type: object
114
+ help: Providing OS/platform-specific properties
115
+ properties:
116
+ exe:
117
+ type: path
118
+ help: The file type of executables, "exe" on Windows and "" on non-Windows
119
+
120
+ loaded:
121
+ type: object internal
122
+ help: Mapping between plugin names and their module objects
123
+
124
+ build_rules:
125
+ type: object
126
+ help: Configuration of rules for build targets.
127
+
128
+ system_requirements:
129
+ type: object
130
+ help: The system requirements for the current project.
131
+
132
+ schema:
133
+ type: object internal
134
+ help: The schema shipped by spin.