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/__init__.py +1340 -0
- csspin/__main__.py +20 -0
- csspin/builtin/__init__.py +322 -0
- csspin/cli.py +711 -0
- csspin/schema.py +204 -0
- csspin/schema.yaml +134 -0
- csspin/tree.py +764 -0
- csspin-2.0.0.dist-info/METADATA +134 -0
- csspin-2.0.0.dist-info/RECORD +13 -0
- csspin-2.0.0.dist-info/WHEEL +5 -0
- csspin-2.0.0.dist-info/entry_points.txt +2 -0
- csspin-2.0.0.dist-info/licenses/LICENSE +176 -0
- csspin-2.0.0.dist-info/top_level.txt +1 -0
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.
|