argus-alm 0.12.10__py3-none-any.whl → 0.13.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.
- argus/client/base.py +1 -1
- argus/client/driver_matrix_tests/cli.py +2 -2
- argus/client/driver_matrix_tests/client.py +1 -1
- argus/client/generic/cli.py +2 -2
- argus/client/sct/client.py +3 -3
- argus/client/sirenada/client.py +1 -1
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/METADATA +2 -4
- argus_alm-0.13.0.dist-info/RECORD +20 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/cli.py +0 -41
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +0 -20
- argus/backend/controller/admin_api.py +0 -354
- argus/backend/controller/api.py +0 -529
- argus/backend/controller/auth.py +0 -67
- argus/backend/controller/client_api.py +0 -108
- argus/backend/controller/main.py +0 -274
- argus/backend/controller/notification_api.py +0 -72
- argus/backend/controller/notifications.py +0 -13
- argus/backend/controller/team.py +0 -126
- argus/backend/controller/team_ui.py +0 -18
- argus/backend/controller/testrun_api.py +0 -482
- argus/backend/controller/view_api.py +0 -162
- argus/backend/db.py +0 -100
- argus/backend/error_handlers.py +0 -21
- argus/backend/events/event_processors.py +0 -34
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/result.py +0 -138
- argus/backend/models/web.py +0 -389
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +0 -225
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -63
- argus/backend/plugins/driver_matrix_tests/model.py +0 -421
- argus/backend/plugins/driver_matrix_tests/plugin.py +0 -22
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
- argus/backend/plugins/driver_matrix_tests/service.py +0 -60
- argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
- argus/backend/plugins/generic/model.py +0 -79
- argus/backend/plugins/generic/plugin.py +0 -16
- argus/backend/plugins/generic/types.py +0 -13
- argus/backend/plugins/loader.py +0 -40
- argus/backend/plugins/sct/controller.py +0 -185
- argus/backend/plugins/sct/plugin.py +0 -38
- argus/backend/plugins/sct/resource_setup.py +0 -178
- argus/backend/plugins/sct/service.py +0 -491
- argus/backend/plugins/sct/testrun.py +0 -272
- argus/backend/plugins/sct/udt.py +0 -101
- argus/backend/plugins/sirenada/model.py +0 -113
- argus/backend/plugins/sirenada/plugin.py +0 -17
- argus/backend/service/admin.py +0 -27
- argus/backend/service/argus_service.py +0 -688
- argus/backend/service/build_system_monitor.py +0 -188
- argus/backend/service/client_service.py +0 -122
- argus/backend/service/event_service.py +0 -18
- argus/backend/service/jenkins_service.py +0 -240
- argus/backend/service/notification_manager.py +0 -150
- argus/backend/service/release_manager.py +0 -230
- argus/backend/service/results_service.py +0 -317
- argus/backend/service/stats.py +0 -540
- argus/backend/service/team_manager_service.py +0 -83
- argus/backend/service/testrun.py +0 -559
- argus/backend/service/user.py +0 -307
- argus/backend/service/views.py +0 -258
- argus/backend/template_filters.py +0 -27
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/argus_web.test.yaml +0 -39
- argus/backend/tests/conftest.py +0 -44
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +0 -70
- argus/backend/util/common.py +0 -65
- argus/backend/util/config.py +0 -38
- argus/backend/util/encoders.py +0 -41
- argus/backend/util/logsetup.py +0 -81
- argus/backend/util/module_loaders.py +0 -30
- argus/backend/util/send_email.py +0 -91
- argus/client/generic_result_old.py +0 -143
- argus/db/.gitkeep +0 -0
- argus/db/argus_json.py +0 -14
- argus/db/cloud_types.py +0 -125
- argus/db/config.py +0 -135
- argus/db/db_types.py +0 -139
- argus/db/interface.py +0 -370
- argus/db/testrun.py +0 -740
- argus/db/utils.py +0 -15
- argus_alm-0.12.10.dist-info/RECORD +0 -96
- /argus/{backend → common}/__init__.py +0 -0
- /argus/{backend/util → common}/enums.py +0 -0
- /argus/{backend/plugins/sct/types.py → common/sct_types.py} +0 -0
- /argus/{backend/plugins/sirenada/types.py → common/sirenada_types.py} +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/LICENSE +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/WHEEL +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/entry_points.txt +0 -0
argus/db/cloud_types.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import ipaddress
|
|
2
|
-
import time
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Optional
|
|
5
|
-
from pydantic.dataclasses import dataclass
|
|
6
|
-
from pydantic import ValidationError, validator
|
|
7
|
-
from argus.db.db_types import ArgusUDTBase
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@dataclass(init=True, repr=True)
|
|
11
|
-
class CloudInstanceDetails(ArgusUDTBase):
|
|
12
|
-
provider: str = ""
|
|
13
|
-
region: str = ""
|
|
14
|
-
public_ip: str = ""
|
|
15
|
-
private_ip: str = ""
|
|
16
|
-
creation_time: int = 0
|
|
17
|
-
termination_time: int = 0
|
|
18
|
-
termination_reason: str = ""
|
|
19
|
-
shards_amount: Optional[int] = 0
|
|
20
|
-
_typename = "CloudInstanceDetails_v3"
|
|
21
|
-
|
|
22
|
-
@classmethod
|
|
23
|
-
def from_db_udt(cls, udt):
|
|
24
|
-
return cls(provider=udt.provider, region=udt.region, public_ip=udt.public_ip, private_ip=udt.private_ip,
|
|
25
|
-
creation_time=udt.creation_time, termination_time=udt.termination_time,
|
|
26
|
-
termination_reason=udt.termination_reason, shards_amount=udt.shards_amount)
|
|
27
|
-
|
|
28
|
-
@validator("public_ip")
|
|
29
|
-
def valid_public_ip_address(cls, v):
|
|
30
|
-
try:
|
|
31
|
-
ipaddress.ip_address(v)
|
|
32
|
-
except ValueError:
|
|
33
|
-
raise ValidationError(f"Not a valid IPv4(v6) address: {v}")
|
|
34
|
-
|
|
35
|
-
return v
|
|
36
|
-
|
|
37
|
-
@validator("private_ip")
|
|
38
|
-
def valid_private_ip_address(cls, v):
|
|
39
|
-
try:
|
|
40
|
-
ipaddress.ip_address(v)
|
|
41
|
-
except ValueError:
|
|
42
|
-
raise ValidationError(f"Not a valid IPv4(v6) address: {v}")
|
|
43
|
-
|
|
44
|
-
return v
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@dataclass(init=True, repr=True)
|
|
48
|
-
class CloudNodesInfo(ArgusUDTBase):
|
|
49
|
-
image_id: str
|
|
50
|
-
instance_type: str
|
|
51
|
-
node_amount: int
|
|
52
|
-
post_behaviour: str
|
|
53
|
-
|
|
54
|
-
@classmethod
|
|
55
|
-
def from_db_udt(cls, udt):
|
|
56
|
-
return cls(image_id=udt.image_id, instance_type=udt.instance_type,
|
|
57
|
-
node_amount=udt.node_amount, post_behaviour=udt.post_behaviour)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@dataclass(init=True, repr=True)
|
|
61
|
-
class BaseCloudSetupDetails(ArgusUDTBase):
|
|
62
|
-
db_node: CloudNodesInfo
|
|
63
|
-
loader_node: CloudNodesInfo
|
|
64
|
-
monitor_node: CloudNodesInfo
|
|
65
|
-
backend: str = None
|
|
66
|
-
_typename = "CloudSetupDetails"
|
|
67
|
-
|
|
68
|
-
@classmethod
|
|
69
|
-
def from_db_udt(cls, udt):
|
|
70
|
-
db_node = CloudNodesInfo(*udt.db_node)
|
|
71
|
-
loader_node = CloudNodesInfo(*udt.loader_node)
|
|
72
|
-
monitor_node = CloudNodesInfo(*udt.monitor_node)
|
|
73
|
-
backend = udt.backend
|
|
74
|
-
return cls(db_node=db_node, loader_node=loader_node, monitor_node=monitor_node, backend=backend)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@dataclass(init=True, repr=True)
|
|
78
|
-
class AWSSetupDetails(BaseCloudSetupDetails):
|
|
79
|
-
backend: str = "aws"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@dataclass(init=True, repr=True)
|
|
83
|
-
class GCESetupDetails(BaseCloudSetupDetails):
|
|
84
|
-
backend: str = "gce"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class ResourceState(str, Enum):
|
|
88
|
-
RUNNING = "running"
|
|
89
|
-
STOPPED = "stopped"
|
|
90
|
-
TERMINATED = "terminated"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@dataclass(init=True, repr=True)
|
|
94
|
-
class CloudResource(ArgusUDTBase):
|
|
95
|
-
name: str
|
|
96
|
-
state: ResourceState
|
|
97
|
-
resource_type: str
|
|
98
|
-
instance_info: CloudInstanceDetails
|
|
99
|
-
_typename = "CloudResource_v3"
|
|
100
|
-
|
|
101
|
-
def __eq__(self, other) -> bool:
|
|
102
|
-
if (isinstance(other, CloudResource)):
|
|
103
|
-
return self.name == other.name
|
|
104
|
-
return False
|
|
105
|
-
|
|
106
|
-
@classmethod
|
|
107
|
-
def from_db_udt(cls, udt):
|
|
108
|
-
instance_info = CloudInstanceDetails.from_db_udt(udt.instance_info)
|
|
109
|
-
return cls(name=udt.name, state=udt.state, resource_type=udt.resource_type, instance_info=instance_info)
|
|
110
|
-
|
|
111
|
-
@validator("state")
|
|
112
|
-
def valid_state(cls, v):
|
|
113
|
-
try:
|
|
114
|
-
ResourceState(v)
|
|
115
|
-
except ValueError:
|
|
116
|
-
raise ValidationError(f"Not a valid resource state: {v}")
|
|
117
|
-
return v
|
|
118
|
-
|
|
119
|
-
def terminate(self, reason):
|
|
120
|
-
self.state = ResourceState.TERMINATED
|
|
121
|
-
self.instance_info.termination_time = int(time.time())
|
|
122
|
-
self.instance_info.termination_reason = reason
|
|
123
|
-
|
|
124
|
-
def stop(self):
|
|
125
|
-
self.state = ResourceState.STOPPED
|
argus/db/config.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from typing import Hashable, Any
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from os import getenv
|
|
5
|
-
import logging
|
|
6
|
-
import yaml
|
|
7
|
-
|
|
8
|
-
LOGGER = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ConfigLocationError(Exception):
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class BaseConfig(ABC):
|
|
16
|
-
def __init__(self):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def as_dict(self) -> dict[Hashable, Any]:
|
|
21
|
-
raise NotImplementedError()
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def username(self) -> str:
|
|
25
|
-
raise NotImplementedError()
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def password(self) -> str:
|
|
29
|
-
raise NotImplementedError()
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def contact_points(self) -> list[str]:
|
|
33
|
-
raise NotImplementedError()
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def keyspace_name(self) -> str:
|
|
37
|
-
raise NotImplementedError()
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def address_mapping(self) -> dict:
|
|
41
|
-
return NotImplementedError()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class FileConfig(BaseConfig):
|
|
45
|
-
@property
|
|
46
|
-
def username(self):
|
|
47
|
-
return self.as_dict.get("username")
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def password(self) -> str:
|
|
51
|
-
return self.as_dict.get("password")
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def contact_points(self) -> list[str]:
|
|
55
|
-
return self.as_dict.get("contact_points")
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def keyspace_name(self) -> str:
|
|
59
|
-
return self.as_dict.get("keyspace_name")
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def address_mapping(self) -> dict:
|
|
63
|
-
return self.as_dict.get("address_mapping")
|
|
64
|
-
|
|
65
|
-
DEFAULT_CONFIG_PATHS = (
|
|
66
|
-
"./config/argus.local.yaml",
|
|
67
|
-
"argus.local.yaml",
|
|
68
|
-
"argus.yaml",
|
|
69
|
-
getenv("HOME") + "/.argus.yaml"
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
def __init__(self, filepath: str = None):
|
|
73
|
-
super().__init__()
|
|
74
|
-
if not filepath:
|
|
75
|
-
for file in self.DEFAULT_CONFIG_PATHS:
|
|
76
|
-
LOGGER.info("Trying %s", file)
|
|
77
|
-
if Path(file).exists():
|
|
78
|
-
filepath = file
|
|
79
|
-
break
|
|
80
|
-
|
|
81
|
-
if not filepath:
|
|
82
|
-
LOGGER.error("All config locations were tried and no config file found")
|
|
83
|
-
raise ConfigLocationError("No config file supplied and no config exists at default location")
|
|
84
|
-
|
|
85
|
-
self.filepath = filepath
|
|
86
|
-
self._credentials = None
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def as_dict(self) -> dict[Hashable, Any]:
|
|
90
|
-
if self._credentials:
|
|
91
|
-
return self._credentials
|
|
92
|
-
path = Path(self.filepath)
|
|
93
|
-
if not path.exists():
|
|
94
|
-
raise ConfigLocationError(f"File not found: {self.filepath}")
|
|
95
|
-
|
|
96
|
-
with open(path.absolute(), "rt", encoding="utf-8") as file:
|
|
97
|
-
self._credentials = yaml.safe_load(file)
|
|
98
|
-
|
|
99
|
-
return self._credentials
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class Config(BaseConfig):
|
|
103
|
-
@property
|
|
104
|
-
def username(self) -> str:
|
|
105
|
-
return self.as_dict.get("username")
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def password(self) -> str:
|
|
109
|
-
return self.as_dict.get("password")
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def contact_points(self) -> list[str]:
|
|
113
|
-
return self.as_dict.get("contact_points")
|
|
114
|
-
|
|
115
|
-
@property
|
|
116
|
-
def keyspace_name(self) -> str:
|
|
117
|
-
return self.as_dict.get("keyspace_name")
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def address_mapping(self) -> dict:
|
|
121
|
-
return self.as_dict.get("address_mapping")
|
|
122
|
-
|
|
123
|
-
def __init__(self, username: str, password: str, contact_points: list[str], keyspace_name: str, address_mapping: dict | None = None):
|
|
124
|
-
super().__init__()
|
|
125
|
-
self._config = {
|
|
126
|
-
"username": username,
|
|
127
|
-
"password": password,
|
|
128
|
-
"contact_points": contact_points,
|
|
129
|
-
"keyspace_name": keyspace_name,
|
|
130
|
-
"address_mapping": address_mapping,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@property
|
|
134
|
-
def as_dict(self) -> dict[Hashable, Any]:
|
|
135
|
-
return self._config
|
argus/db/db_types.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import time
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Any, Union, Type, TypeVar, Optional
|
|
5
|
-
from pydantic.dataclasses import dataclass
|
|
6
|
-
from pydantic import validator, ValidationError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ArgusUDTBase:
|
|
10
|
-
_typename = None
|
|
11
|
-
|
|
12
|
-
@classmethod
|
|
13
|
-
def basename(cls):
|
|
14
|
-
return cls._typename if cls._typename else cls.__name__
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@dataclass(init=True, repr=True)
|
|
18
|
-
class NodeDescription(ArgusUDTBase):
|
|
19
|
-
name: str
|
|
20
|
-
ip: str
|
|
21
|
-
shards: int
|
|
22
|
-
|
|
23
|
-
@classmethod
|
|
24
|
-
def from_db_udt(cls, udt):
|
|
25
|
-
return cls(name=udt.name, ip=udt.ip, shards=udt.shards)
|
|
26
|
-
|
|
27
|
-
@validator("ip")
|
|
28
|
-
def valid_ip_address(cls, value):
|
|
29
|
-
ip_addr_re = r"(\d{1,3}\.){3}\d{1,3}"
|
|
30
|
-
if not re.match(ip_addr_re, value):
|
|
31
|
-
raise ValidationError(f"Not a valid ip address: {value}")
|
|
32
|
-
|
|
33
|
-
ip_by_octets = [int(octet) for octet in value.split(".") if int(octet) <= 255]
|
|
34
|
-
if len(ip_by_octets) != 4:
|
|
35
|
-
raise ValidationError(f"Octets out of range (0, 255): {value}")
|
|
36
|
-
|
|
37
|
-
return value
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass(init=True, repr=True)
|
|
41
|
-
class PackageVersion(ArgusUDTBase):
|
|
42
|
-
name: str
|
|
43
|
-
version: str
|
|
44
|
-
date: str
|
|
45
|
-
revision_id: str
|
|
46
|
-
build_id: Optional[str] = ""
|
|
47
|
-
_typename = "PackageVersion_v2"
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
def from_db_udt(cls, udt):
|
|
51
|
-
return cls(name=udt.name, version=udt.version,
|
|
52
|
-
date=udt.date, revision_id=udt.revision_id, build_id=udt.build_id)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class NemesisStatus(str, Enum):
|
|
56
|
-
STARTED = "started"
|
|
57
|
-
RUNNING = "running"
|
|
58
|
-
FAILED = "failed"
|
|
59
|
-
SKIPPED = "skipped"
|
|
60
|
-
SUCCEEDED = "succeeded"
|
|
61
|
-
TERMINATED = "terminated"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class TestStatus(str, Enum):
|
|
65
|
-
CREATED = "created"
|
|
66
|
-
RUNNING = "running"
|
|
67
|
-
FAILED = "failed"
|
|
68
|
-
PASSED = "passed"
|
|
69
|
-
ABORTED = "aborted"
|
|
70
|
-
NOT_PLANNED = "not_planned"
|
|
71
|
-
NOT_RUN = "not_run"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class TestInvestigationStatus(str, Enum):
|
|
75
|
-
NOT_INVESTIGATED = "not_investigated"
|
|
76
|
-
IN_PROGRESS = "in_progress"
|
|
77
|
-
INVESTIGATED = "investigated"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@dataclass(init=True, repr=True)
|
|
81
|
-
class NemesisRunInfo(ArgusUDTBase):
|
|
82
|
-
class_name: str
|
|
83
|
-
name: str
|
|
84
|
-
duration: int
|
|
85
|
-
target_node: NodeDescription
|
|
86
|
-
status: str
|
|
87
|
-
start_time: int
|
|
88
|
-
end_time: int = 0
|
|
89
|
-
stack_trace: str = ""
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def nemesis_status(self):
|
|
93
|
-
return NemesisStatus(self.status)
|
|
94
|
-
|
|
95
|
-
@nemesis_status.setter
|
|
96
|
-
def nemesis_status(self, value: NemesisStatus):
|
|
97
|
-
self.status = NemesisStatus(value).value
|
|
98
|
-
|
|
99
|
-
@classmethod
|
|
100
|
-
def from_db_udt(cls, udt):
|
|
101
|
-
target_node = NodeDescription.from_db_udt(udt.target_node)
|
|
102
|
-
return cls(class_name=udt.class_name, name=udt.name, duration=udt.duration,
|
|
103
|
-
target_node=target_node, status=udt.status, start_time=udt.start_time, end_time=udt.end_time,
|
|
104
|
-
stack_trace=udt.stack_trace)
|
|
105
|
-
|
|
106
|
-
def complete(self, stack_trace=None):
|
|
107
|
-
self.end_time = int(time.time())
|
|
108
|
-
if stack_trace:
|
|
109
|
-
self.nemesis_status = NemesisStatus.FAILED
|
|
110
|
-
self.stack_trace = stack_trace
|
|
111
|
-
else:
|
|
112
|
-
self.nemesis_status = NemesisStatus.SUCCEEDED
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@dataclass(init=True, repr=True)
|
|
116
|
-
class EventsBySeverity(ArgusUDTBase):
|
|
117
|
-
severity: str
|
|
118
|
-
event_amount: int
|
|
119
|
-
last_events: list[str]
|
|
120
|
-
|
|
121
|
-
@classmethod
|
|
122
|
-
def from_db_udt(cls, udt):
|
|
123
|
-
return cls(severity=udt.severity, event_amount=udt.event_amount, last_events=udt.last_events)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
TypeHint = TypeVar("TypeHint")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@dataclass(init=True, repr=True)
|
|
130
|
-
class CollectionHint:
|
|
131
|
-
stored_type: TypeHint
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@dataclass(init=True, repr=True)
|
|
135
|
-
class ColumnInfo:
|
|
136
|
-
name: str
|
|
137
|
-
type: Type[Union[CollectionHint, int, str, TypeHint]]
|
|
138
|
-
value: Any
|
|
139
|
-
constraints: list[str]
|