smartmodels 0.1.0__py2.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.
Potentially problematic release.
This version of smartmodels might be problematic. Click here for more details.
- smartmodels/__init__.py +5 -0
- smartmodels/cli/__init__.py +36 -0
- smartmodels/cli/config.py +151 -0
- smartmodels/cli/main.py +57 -0
- smartmodels/cli/setup.py +159 -0
- smartmodels/cli/wingdbstub.py +2 -0
- smartmodels/cli/workspace.py +119 -0
- smartmodels/helpers.py +248 -0
- smartmodels/smartmodels.py +31 -0
- smartmodels-0.1.0.dist-info/AUTHORS.rst +13 -0
- smartmodels-0.1.0.dist-info/LICENSE +22 -0
- smartmodels-0.1.0.dist-info/METADATA +87 -0
- smartmodels-0.1.0.dist-info/RECORD +16 -0
- smartmodels-0.1.0.dist-info/WHEEL +6 -0
- smartmodels-0.1.0.dist-info/entry_points.txt +3 -0
- smartmodels-0.1.0.dist-info/top_level.txt +1 -0
smartmodels/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""CLI for 'SmartModels' package.
|
|
2
|
+
|
|
3
|
+
# Autocompletion
|
|
4
|
+
|
|
5
|
+
https://click.palletsprojects.com/en/8.1.x/shell-completion/
|
|
6
|
+
|
|
7
|
+
_SMARTMODELS_COMPLETE=bash_source smartmodels > ~/.smartmodels-complete.bash
|
|
8
|
+
|
|
9
|
+
. ~/.smartmodels-complete.bash
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
# -----------------------------------------------
|
|
16
|
+
# import main cli interface (root)
|
|
17
|
+
# -----------------------------------------------
|
|
18
|
+
|
|
19
|
+
from .main import *
|
|
20
|
+
from .config import *
|
|
21
|
+
from .workspace import *
|
|
22
|
+
|
|
23
|
+
# -----------------------------------------------
|
|
24
|
+
# import other project submodules/subcommands
|
|
25
|
+
# -----------------------------------------------
|
|
26
|
+
|
|
27
|
+
# from .inventory import inventory
|
|
28
|
+
# from .plan import plan
|
|
29
|
+
# from .real import real
|
|
30
|
+
# from .roles import role
|
|
31
|
+
# from .run import run
|
|
32
|
+
# from .target import target
|
|
33
|
+
# from .test import test
|
|
34
|
+
# from .users import user
|
|
35
|
+
# from .watch import watch
|
|
36
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from ..helpers import *
|
|
7
|
+
from .main import *
|
|
8
|
+
|
|
9
|
+
# https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
|
|
10
|
+
RED = "\033[31;1;4m"
|
|
11
|
+
GREEN = "\033[32;1;4m"
|
|
12
|
+
YELLOW = "\033[33;1;4m"
|
|
13
|
+
BLUE = "\033[34;1;4m"
|
|
14
|
+
PINK = "\033[35;1;4m"
|
|
15
|
+
|
|
16
|
+
RESET = "\033[0m"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@main.group(context_settings=CONTEXT_SETTINGS)
|
|
20
|
+
@click.pass_obj
|
|
21
|
+
def config(env):
|
|
22
|
+
"""A new group named 'config' nested to 'main' group
|
|
23
|
+
Whenever a 'config' subcommand is called, this code
|
|
24
|
+
will be executed before and load or create a config file
|
|
25
|
+
by default.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
load_config(env)
|
|
30
|
+
|
|
31
|
+
except Exception as why:
|
|
32
|
+
# If load_config fails, then create a clean one.
|
|
33
|
+
print(f"{why}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
setdefault(
|
|
37
|
+
env,
|
|
38
|
+
"includes",
|
|
39
|
+
{
|
|
40
|
+
r"smartmodels/.*\.yaml$": None,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
setdefault(
|
|
44
|
+
env,
|
|
45
|
+
"excludes",
|
|
46
|
+
{},
|
|
47
|
+
)
|
|
48
|
+
setdefault(
|
|
49
|
+
env,
|
|
50
|
+
"folders", # Not regexp
|
|
51
|
+
{
|
|
52
|
+
#'~/Documents': None,
|
|
53
|
+
#'~/workspace': None,
|
|
54
|
+
".": None,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
setdefault(
|
|
58
|
+
env,
|
|
59
|
+
"resource",
|
|
60
|
+
os.path.join(env.home, "resources.yaml"),
|
|
61
|
+
)
|
|
62
|
+
setdefault(
|
|
63
|
+
env,
|
|
64
|
+
"role",
|
|
65
|
+
os.path.join(env.home, "roles.yaml"),
|
|
66
|
+
)
|
|
67
|
+
# if old != env.__dict__:
|
|
68
|
+
# save_config(env)
|
|
69
|
+
|
|
70
|
+
save_config(env)
|
|
71
|
+
return env
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@config.command()
|
|
75
|
+
@click.pass_obj
|
|
76
|
+
def list(env):
|
|
77
|
+
banner("Config", env.__dict__)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@config.command()
|
|
81
|
+
@click.option("--include", default=None)
|
|
82
|
+
@click.option("--exclude", default=None)
|
|
83
|
+
@click.option("--folder", default=None)
|
|
84
|
+
@click.pass_obj
|
|
85
|
+
def view(env, include, exclude, folder):
|
|
86
|
+
if include:
|
|
87
|
+
click.echo(f"add include: {include}")
|
|
88
|
+
s = setdefault(env, "includes", dict())
|
|
89
|
+
s[include] = None
|
|
90
|
+
save_config(env)
|
|
91
|
+
if exclude:
|
|
92
|
+
click.echo(f"add exclude: {exclude}")
|
|
93
|
+
s = setdefault(env, "excludes", dict())
|
|
94
|
+
s[exclude] = None
|
|
95
|
+
save_config(env)
|
|
96
|
+
if folder:
|
|
97
|
+
click.echo(f"add folder: {folder}")
|
|
98
|
+
s = setdefault(env, "folders", dict())
|
|
99
|
+
s[folder] = None
|
|
100
|
+
save_config(env)
|
|
101
|
+
list.callback()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@config.command()
|
|
107
|
+
@click.option("--include", default=None)
|
|
108
|
+
@click.option("--exclude", default=None)
|
|
109
|
+
@click.option("--folder", default=None)
|
|
110
|
+
@click.pass_obj
|
|
111
|
+
def add(env, include, exclude, folder):
|
|
112
|
+
if include:
|
|
113
|
+
click.echo(f"add include: {include}")
|
|
114
|
+
s = setdefault(env, "includes", dict())
|
|
115
|
+
s[include] = None
|
|
116
|
+
save_config(env)
|
|
117
|
+
if exclude:
|
|
118
|
+
click.echo(f"add exclude: {exclude}")
|
|
119
|
+
s = setdefault(env, "excludes", dict())
|
|
120
|
+
s[exclude] = None
|
|
121
|
+
save_config(env)
|
|
122
|
+
if folder:
|
|
123
|
+
click.echo(f"add folder: {folder}")
|
|
124
|
+
s = setdefault(env, "folders", dict())
|
|
125
|
+
s[folder] = None
|
|
126
|
+
save_config(env)
|
|
127
|
+
list.callback()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@config.command()
|
|
131
|
+
@click.option("--include", default=None)
|
|
132
|
+
@click.option("--exclude", default=None)
|
|
133
|
+
@click.option("--folder", default=None)
|
|
134
|
+
@click.pass_obj
|
|
135
|
+
def delete(env, include, exclude, folder):
|
|
136
|
+
if include:
|
|
137
|
+
click.echo(f"delete include: {include}")
|
|
138
|
+
s = setdefault(env, "includes", dict())
|
|
139
|
+
s.pop(include, None)
|
|
140
|
+
save_config(env)
|
|
141
|
+
if exclude:
|
|
142
|
+
click.echo(f"add exclude: {exclude}")
|
|
143
|
+
s = setdefault(env, "excludes", dict())
|
|
144
|
+
s.pop(exclude, None)
|
|
145
|
+
save_config(env)
|
|
146
|
+
if folder:
|
|
147
|
+
click.echo(f"add folder: {folder}")
|
|
148
|
+
s = setdefault(env, "folders", dict())
|
|
149
|
+
s.pop(folder, None)
|
|
150
|
+
save_config(env)
|
|
151
|
+
list.callback()
|
smartmodels/cli/main.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Console script for any click extensible CLI."""
|
|
2
|
+
import os
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
CONTEXT_SETTINGS = dict(default_map={'runserver': {'port': 5000}})
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Env(object):
|
|
9
|
+
def __init__(self, home=None, debug=False):
|
|
10
|
+
home = os.path.expandvars(home)
|
|
11
|
+
home = os.path.expanduser(home)
|
|
12
|
+
home = os.path.abspath(home)
|
|
13
|
+
|
|
14
|
+
self.home = os.path.abspath(home)
|
|
15
|
+
self.cwd = os.path.abspath('.')
|
|
16
|
+
if self.cwd != self.home:
|
|
17
|
+
self.config_folders = [self.cwd, self.home]
|
|
18
|
+
else:
|
|
19
|
+
self.config_folders = [self.cwd]
|
|
20
|
+
|
|
21
|
+
self.config_files = [
|
|
22
|
+
os.path.join(path, 'config.yaml') for path in self.config_folders
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
self.folders = []
|
|
26
|
+
self.debug = debug
|
|
27
|
+
self.autoassign_pattern = r'z.*\d+@'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.group(context_settings=CONTEXT_SETTINGS)
|
|
31
|
+
@click.option('--config-folder', envvar='SMARTMODELS_HOME', default='~/.config/smartmodels')
|
|
32
|
+
@click.option('--debug/--no-debug', default=False, envvar='REPO_DEBUG')
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def main(ctx, config_folder, debug):
|
|
35
|
+
ctx.obj = Env(config_folder, debug)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@main.command()
|
|
39
|
+
@click.option('--port', default=8000)
|
|
40
|
+
@click.pass_obj
|
|
41
|
+
def add(env, port):
|
|
42
|
+
click.echo(f"Env: {env}/")
|
|
43
|
+
click.echo(f"Serving on http://127.0.0.1:{port}/")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@main.command()
|
|
47
|
+
@click.option('--port', default=8000)
|
|
48
|
+
@click.pass_obj
|
|
49
|
+
def delete(env, port):
|
|
50
|
+
click.echo(f"Serving on http://127.0.0.1:{port}/")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@main.command()
|
|
54
|
+
@click.option('--port', default=8000)
|
|
55
|
+
@click.pass_obj
|
|
56
|
+
def run(env, port):
|
|
57
|
+
click.echo(f"Serving on http://127.0.0.1:{port}/")
|
smartmodels/cli/setup.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import time
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from distutils import command
|
|
8
|
+
|
|
9
|
+
# import os
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
# import yaml
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
# from pycelium.tools import soft
|
|
17
|
+
# from pycelium.tools.cli.inventory import credentials, expand_network
|
|
18
|
+
# from pycelium.tools.cli.config import (
|
|
19
|
+
# # config,
|
|
20
|
+
# # banner,
|
|
21
|
+
# RED,
|
|
22
|
+
# RESET,
|
|
23
|
+
# BLUE,
|
|
24
|
+
# PINK,
|
|
25
|
+
# YELLOW,
|
|
26
|
+
# GREEN,
|
|
27
|
+
# )
|
|
28
|
+
from .main import *
|
|
29
|
+
from .config import *
|
|
30
|
+
from ..helpers import ConfigHelper
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@main.group(context_settings=CONTEXT_SETTINGS)
|
|
34
|
+
@click.pass_obj
|
|
35
|
+
def setup(env):
|
|
36
|
+
"""subcommands for manage workspaces for smartmodels"""
|
|
37
|
+
# banner("User", env.__dict__)
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def find_venv():
|
|
42
|
+
for path in sys.path:
|
|
43
|
+
m = re.match(
|
|
44
|
+
r"(?P<virtualenv>.*/(venv|env|virtualenv))/lib/python[^/]+/(site-packages)$",
|
|
45
|
+
path,
|
|
46
|
+
)
|
|
47
|
+
if m:
|
|
48
|
+
return m.groupdict()["virtualenv"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def configure_unit(unit_content, service_name, service_type="service", restart=True):
|
|
52
|
+
# Write the service unit to a file
|
|
53
|
+
_tmp_file = f"/tmp/{service_name}.{service_type}"
|
|
54
|
+
unit_file_path = f"/etc/systemd/system/{service_name}.{service_type}"
|
|
55
|
+
with open(_tmp_file, "w") as unit_file:
|
|
56
|
+
unit_file.write(unit_content)
|
|
57
|
+
subprocess.run(["sudo", "mv", _tmp_file, unit_file_path])
|
|
58
|
+
|
|
59
|
+
# Reload systemd to apply changes
|
|
60
|
+
subprocess.run(["sudo", "systemctl", "daemon-reload"])
|
|
61
|
+
|
|
62
|
+
# Enable and start the service
|
|
63
|
+
subprocess.run(["sudo", "systemctl", "enable", service_name])
|
|
64
|
+
if restart:
|
|
65
|
+
subprocess.run(["sudo", "systemctl", "restart", service_name])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _service(env, command):
|
|
69
|
+
"""List existing workspaces for smartmodels"""
|
|
70
|
+
|
|
71
|
+
# Specify the service name
|
|
72
|
+
service_name = __file__.split("/")[-3]
|
|
73
|
+
|
|
74
|
+
# user and group
|
|
75
|
+
user = group = os.getlogin()
|
|
76
|
+
home_dir = os.path.expanduser("~")
|
|
77
|
+
# where is executable
|
|
78
|
+
virtualenv = find_venv()
|
|
79
|
+
|
|
80
|
+
if virtualenv is not None:
|
|
81
|
+
exec_start = (
|
|
82
|
+
f"{virtualenv}/bin/python {virtualenv}/bin/{service_name} {command}"
|
|
83
|
+
)
|
|
84
|
+
environment = f"Environment='PATH={virtualenv}/bin'"
|
|
85
|
+
else:
|
|
86
|
+
exec_start = f"{home_dir}/.local/bin/{service_name} {command}"
|
|
87
|
+
environment = ""
|
|
88
|
+
|
|
89
|
+
# Define the service unit configuration
|
|
90
|
+
service_unit = f"""
|
|
91
|
+
[Unit]
|
|
92
|
+
Description=smartmodels Supervisor Service
|
|
93
|
+
After=network.target
|
|
94
|
+
|
|
95
|
+
[Service]
|
|
96
|
+
ExecStart={exec_start}
|
|
97
|
+
{environment}
|
|
98
|
+
|
|
99
|
+
ProtectHome=false
|
|
100
|
+
WorkingDirectory={home_dir}/workspace/{service_name}
|
|
101
|
+
#Restart=always
|
|
102
|
+
Restart=no
|
|
103
|
+
#RestartSec=5s
|
|
104
|
+
User={user}
|
|
105
|
+
Group={group}
|
|
106
|
+
|
|
107
|
+
[Install]
|
|
108
|
+
WantedBy=multi-user.target
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
timer_unit = f"""
|
|
112
|
+
[Unit]
|
|
113
|
+
Description=smartmodels reset
|
|
114
|
+
|
|
115
|
+
[Timer]
|
|
116
|
+
Unit={service_name}.service
|
|
117
|
+
#OnCalendar=hourly
|
|
118
|
+
#OnCalendar=*:0/30
|
|
119
|
+
#OnCalendar=*:0/5
|
|
120
|
+
|
|
121
|
+
#OnBootSec=10min
|
|
122
|
+
#RandomizedDelaySec=15min
|
|
123
|
+
#OnActiveSec=15min
|
|
124
|
+
#OnCalendar=*-*-* 0/8:00:00
|
|
125
|
+
OnCalendar=*-*-* 00:00:00
|
|
126
|
+
|
|
127
|
+
[Install]
|
|
128
|
+
WantedBy=timers.target
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
configure_unit(service_unit, service_name, restart=False)
|
|
132
|
+
configure_unit(timer_unit, service_name, service_type="timer")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@setup.command()
|
|
136
|
+
@click.option("--command")
|
|
137
|
+
@click.pass_obj
|
|
138
|
+
def service(env, command):
|
|
139
|
+
"""Install a reset service smartmodels"""
|
|
140
|
+
# force config loading
|
|
141
|
+
config.callback()
|
|
142
|
+
|
|
143
|
+
_service(env, command)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@setup.command()
|
|
147
|
+
@click.pass_obj
|
|
148
|
+
def autocomplete(env):
|
|
149
|
+
"""Create bash auto-completion script"""
|
|
150
|
+
# force config loading
|
|
151
|
+
config.callback()
|
|
152
|
+
subprocess.run(
|
|
153
|
+
[
|
|
154
|
+
"_SMARTMODELS_COMPLETE=bash_source smartmodels > ~/.smartmodels-complete.bash"
|
|
155
|
+
],
|
|
156
|
+
shell=True,
|
|
157
|
+
)
|
|
158
|
+
print("for activation use:")
|
|
159
|
+
print("source ~/.smartmodels-complete.bash")
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from .main import *
|
|
8
|
+
from .config import *
|
|
9
|
+
|
|
10
|
+
@main.group(context_settings=CONTEXT_SETTINGS)
|
|
11
|
+
@click.pass_obj
|
|
12
|
+
def workspace(env):
|
|
13
|
+
"""subcommands for manae workspaces for smartmodels"""
|
|
14
|
+
# banner("User", env.__dict__)
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@workspace.command()
|
|
19
|
+
@click.option("--path", default=None)
|
|
20
|
+
@click.pass_obj
|
|
21
|
+
def new(env, path):
|
|
22
|
+
"""Create a new workspace for smartmodels"""
|
|
23
|
+
# force config loading
|
|
24
|
+
config.callback()
|
|
25
|
+
|
|
26
|
+
# TODO: add your new workspace configuratoin folder here ...
|
|
27
|
+
if not path:
|
|
28
|
+
path = "."
|
|
29
|
+
|
|
30
|
+
root = expandpath(path)
|
|
31
|
+
print(f"Creating / updating workspace in {root}")
|
|
32
|
+
os.makedirs(root, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# database for stats
|
|
35
|
+
stats_path = os.path.join(root, 'stats.yaml')
|
|
36
|
+
gitlab_cfg_path = os.path.join(root, '.python-gitlab.cfg')
|
|
37
|
+
db_path = os.path.join(root, 'db')
|
|
38
|
+
if not os.path.exists(stats_path):
|
|
39
|
+
db = {
|
|
40
|
+
'kpi_1': 0.73,
|
|
41
|
+
'kpi_2': 0,
|
|
42
|
+
}
|
|
43
|
+
yaml.dump(db, open(stats_path, 'wt'), Dumper=yaml.Dumper)
|
|
44
|
+
|
|
45
|
+
# config file
|
|
46
|
+
config_path = os.path.join(root, 'config.yaml')
|
|
47
|
+
if not os.path.exists(config_path):
|
|
48
|
+
db = {
|
|
49
|
+
'templates': {
|
|
50
|
+
'compiled': {
|
|
51
|
+
'{root}/{reldir}/compiled/{basename}.json': [
|
|
52
|
+
r'(?P<dirname>.*)[/\/](?P<basename>(?P<name>.*?)(?P<ext>\.[^\.]+$))'
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
'error': {
|
|
56
|
+
'{root}/error/{reldir}/{basename}': [
|
|
57
|
+
r'(?P<dirname>.*)[/\/](?P<basename>(?P<name>.*?)(?P<ext>\.[^\.]+$))'
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
'stats': stats_path,
|
|
62
|
+
'db': db_path,
|
|
63
|
+
'folders': {
|
|
64
|
+
'data': f'{root}/data/',
|
|
65
|
+
},
|
|
66
|
+
'gitlab_cfg_path': gitlab_cfg_path,
|
|
67
|
+
'gitlab_instance': 'spec',
|
|
68
|
+
'num_threads': 8,
|
|
69
|
+
}
|
|
70
|
+
yaml.dump(db, open(config_path, 'wt'), Dumper=yaml.Dumper)
|
|
71
|
+
|
|
72
|
+
# check folder in config file
|
|
73
|
+
cfg = yaml.load(open(config_path, 'rt'), Loader=yaml.Loader)
|
|
74
|
+
|
|
75
|
+
# create working folders
|
|
76
|
+
for name in cfg['folders'].values():
|
|
77
|
+
path = os.path.join(root, name)
|
|
78
|
+
os.makedirs(path, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
# check .env file
|
|
81
|
+
env_path = os.path.join(root, '.env')
|
|
82
|
+
if not os.path.exists(env_path):
|
|
83
|
+
content = f"""# ENV variables
|
|
84
|
+
PYTHON_GITLAB_CFG={gitlab_cfg_path}
|
|
85
|
+
OPENAI_API_KEY=
|
|
86
|
+
"""
|
|
87
|
+
open(env_path, 'wt').write(content)
|
|
88
|
+
|
|
89
|
+
# check gitlab_cfg_path file
|
|
90
|
+
if not os.path.exists(gitlab_cfg_path):
|
|
91
|
+
content = f"""
|
|
92
|
+
[global]
|
|
93
|
+
default = spec
|
|
94
|
+
ssl_verify = true
|
|
95
|
+
timeout = 10
|
|
96
|
+
|
|
97
|
+
[spec]
|
|
98
|
+
url = https://git.spec-cibernos.com
|
|
99
|
+
private_token = glpat-ZZ_TDbasg1CsyCsa4ihG
|
|
100
|
+
api_version = 4
|
|
101
|
+
|
|
102
|
+
[elsewhere]
|
|
103
|
+
url = http://else.whe.re:8080
|
|
104
|
+
private_token = helper: path/to/helper.sh
|
|
105
|
+
timeout = 1
|
|
106
|
+
"""
|
|
107
|
+
open(gitlab_cfg_path, 'wt').write(content)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@workspace.command()
|
|
111
|
+
@click.pass_obj
|
|
112
|
+
def list(env):
|
|
113
|
+
"""List existing workspaces for smartmodels"""
|
|
114
|
+
# force config loading
|
|
115
|
+
config.callback()
|
|
116
|
+
|
|
117
|
+
# TODO: add your new workspace configuration folder here ...
|
|
118
|
+
|
|
119
|
+
|
smartmodels/helpers.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
import string
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
from dateutil import parser
|
|
7
|
+
from glom import glom, assign
|
|
8
|
+
#import jmespath
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ------------------------------------------------
|
|
12
|
+
# File and config helpers
|
|
13
|
+
# ------------------------------------------------
|
|
14
|
+
def expandpath(path):
|
|
15
|
+
if path:
|
|
16
|
+
path = os.path.expanduser(path)
|
|
17
|
+
path = os.path.expandvars(path)
|
|
18
|
+
path = os.path.abspath(path)
|
|
19
|
+
while path[-1] == "/":
|
|
20
|
+
path = path[:-1]
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
def load_config(env):
|
|
24
|
+
"""Merge config files"""
|
|
25
|
+
cfg = env.__dict__
|
|
26
|
+
for path in reversed(env.config_files):
|
|
27
|
+
try:
|
|
28
|
+
data = yaml.load(open(path, "rt"), Loader=yaml.Loader)
|
|
29
|
+
# merge(cfg, data, inplace=True) # use any deep merge library or ...
|
|
30
|
+
cfg.update(data)
|
|
31
|
+
|
|
32
|
+
except FileNotFoundError:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
env.folders = {expandpath(p): None for p in env.folders}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def save_config(env):
|
|
39
|
+
os.makedirs(os.path.dirname(env.config_file), exist_ok=True)
|
|
40
|
+
yaml.dump(env.__dict__, open(env.config_file, "wt"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --------------------------------------------------
|
|
45
|
+
# Convert Base
|
|
46
|
+
# --------------------------------------------------
|
|
47
|
+
|
|
48
|
+
# CHAR_LOOKUP = list(string.digits + string.ascii_letters)
|
|
49
|
+
|
|
50
|
+
# avoid use of numbers (so can be used as regular attribute names with ".")
|
|
51
|
+
CHAR_LOOKUP = list(string.ascii_letters)
|
|
52
|
+
INV_LOOKUP = {c: i for i, c in enumerate(CHAR_LOOKUP)}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def convert_base(number, base, padding=-1, lookup=CHAR_LOOKUP):
|
|
56
|
+
"""Coding a number into a string in base 'base'
|
|
57
|
+
|
|
58
|
+
results will be padded with '0' until minimal 'padding'
|
|
59
|
+
length is reached.
|
|
60
|
+
|
|
61
|
+
lookup is the char map available for coding.
|
|
62
|
+
"""
|
|
63
|
+
if base < 2 or base > len(lookup):
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"base: {base} > coding map length: {len(lookup)}"
|
|
66
|
+
)
|
|
67
|
+
mods = []
|
|
68
|
+
while number > 0:
|
|
69
|
+
mods.append(lookup[number % base])
|
|
70
|
+
number //= base
|
|
71
|
+
|
|
72
|
+
while len(mods) < padding:
|
|
73
|
+
mods.append(lookup[0])
|
|
74
|
+
|
|
75
|
+
mods.reverse()
|
|
76
|
+
return ''.join(mods)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def from_base(key, base, inv_lookup=INV_LOOKUP):
|
|
80
|
+
"""Convert a coded number in base 'base' to an integer."""
|
|
81
|
+
number = 0
|
|
82
|
+
keys = list(key)
|
|
83
|
+
keys.reverse()
|
|
84
|
+
w = 1
|
|
85
|
+
for c in keys:
|
|
86
|
+
number += INV_LOOKUP[c] * w
|
|
87
|
+
w *= base
|
|
88
|
+
return number
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# def new_uid(base=50):
|
|
92
|
+
# number = uuid.uuid1()
|
|
93
|
+
# return convert_base(number.int, base)
|
|
94
|
+
SEED = 12345
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def new_uid(base=50):
|
|
98
|
+
global SEED
|
|
99
|
+
SEED += 1
|
|
100
|
+
return convert_base(SEED, base)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# from xml.sax.saxutils import escape
|
|
104
|
+
# ------------------------------------------------
|
|
105
|
+
# jinja2 filters
|
|
106
|
+
# ------------------------------------------------
|
|
107
|
+
def escape(text: str):
|
|
108
|
+
text = text.replace("&", "&")
|
|
109
|
+
text = text.replace("<", "<")
|
|
110
|
+
text = text.replace(">", ">")
|
|
111
|
+
text = text.replace('"', """)
|
|
112
|
+
text = text.replace("'", "'")
|
|
113
|
+
return text
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def fmt(value, fmt=">40"):
|
|
117
|
+
fmt = "{:" + fmt + "}"
|
|
118
|
+
try:
|
|
119
|
+
value = fmt.format(value)
|
|
120
|
+
except:
|
|
121
|
+
pass
|
|
122
|
+
return value
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------
|
|
128
|
+
# glom extensions
|
|
129
|
+
# ------------------------------------------------
|
|
130
|
+
def setdefault(obj, path, val, missing=dict):
|
|
131
|
+
current = glom(obj, path, default=None)
|
|
132
|
+
if current is None:
|
|
133
|
+
assign(obj, path, val, missing=missing)
|
|
134
|
+
return val
|
|
135
|
+
return current
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------
|
|
139
|
+
# Converter functions
|
|
140
|
+
# ------------------------------------------------
|
|
141
|
+
def I(x):
|
|
142
|
+
return x
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def INT(x):
|
|
146
|
+
return int(x)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def FLOAT(x):
|
|
150
|
+
return float(x)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def BOOL(x):
|
|
154
|
+
return x.lower() in ("true", "yes")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def TEXT(x):
|
|
158
|
+
return x.text
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def DATE(x): # TODO
|
|
162
|
+
return parser.parse(x)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def DURATION(x): # TODO
|
|
166
|
+
return timedelta(days=float(x))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def COLOR(x):
|
|
170
|
+
"""Task color
|
|
171
|
+
Ignore when if a "black" or "blue" color and let GP
|
|
172
|
+
use default ones next time.
|
|
173
|
+
"""
|
|
174
|
+
if x not in ("#8cb6ce", "#000000"):
|
|
175
|
+
return x
|
|
176
|
+
return x # TODO: remove, this hack will remove default colors
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def PRIORITY(x):
|
|
180
|
+
"""GanttProject PRIORITY.... (have not sense :) )"""
|
|
181
|
+
return {
|
|
182
|
+
"3": -2, # Lowest
|
|
183
|
+
"0": -1, # Low
|
|
184
|
+
None: 0, # Normal (missing)
|
|
185
|
+
"2": 1, # High
|
|
186
|
+
"4": 2, # Highest
|
|
187
|
+
}.get(x, 0)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ------------------------------------------------
|
|
191
|
+
# console
|
|
192
|
+
# ------------------------------------------------
|
|
193
|
+
|
|
194
|
+
GREEN = "\033[32;1;4m"
|
|
195
|
+
RESET = "\033[0m"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
last_sepatator = 40
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def banner(
|
|
202
|
+
header,
|
|
203
|
+
lines=None,
|
|
204
|
+
spec=None,
|
|
205
|
+
sort_by=None,
|
|
206
|
+
sort_reverse=True,
|
|
207
|
+
output=print,
|
|
208
|
+
color=GREEN,
|
|
209
|
+
):
|
|
210
|
+
global last_sepatator
|
|
211
|
+
lines = lines or []
|
|
212
|
+
# compute keys spaces
|
|
213
|
+
m = 1 + max([len(k) for k in lines] or [0])
|
|
214
|
+
if isinstance(lines, dict):
|
|
215
|
+
if sort_by:
|
|
216
|
+
idx = 0 if sort_by.lower().startswith("keys") else 1
|
|
217
|
+
lines = dict(
|
|
218
|
+
sorted(
|
|
219
|
+
lines.items(),
|
|
220
|
+
key=lambda item: item[idx],
|
|
221
|
+
reverse=sort_reverse,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
_lines = []
|
|
225
|
+
for k, v in lines.items():
|
|
226
|
+
if spec:
|
|
227
|
+
try:
|
|
228
|
+
v = glom(v, spec)
|
|
229
|
+
except:
|
|
230
|
+
v = getattr(v, spec)
|
|
231
|
+
|
|
232
|
+
line = f"{k.ljust(m)}: {v}"
|
|
233
|
+
_lines.append(line)
|
|
234
|
+
lines = _lines
|
|
235
|
+
|
|
236
|
+
if lines:
|
|
237
|
+
m = max([len(l) for l in lines])
|
|
238
|
+
last_sepatator = m
|
|
239
|
+
elif last_sepatator:
|
|
240
|
+
m = last_sepatator
|
|
241
|
+
else:
|
|
242
|
+
m = max([40, len(header)]) - len(header) + 1
|
|
243
|
+
|
|
244
|
+
# m = max([len(l) for l in lines] or [40, len(header)]) - len(header) + 1
|
|
245
|
+
output(f"{color}{header}{' ' * m}{RESET}")
|
|
246
|
+
for line in lines:
|
|
247
|
+
output(line)
|
|
248
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Main module."""
|
|
2
|
+
|
|
3
|
+
# libraty modules
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
# library partial
|
|
8
|
+
from time import sleep
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# local imports
|
|
12
|
+
from .helpers import parse_uri
|
|
13
|
+
|
|
14
|
+
# 3rd party libraries
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------
|
|
18
|
+
# Loggers
|
|
19
|
+
# ---------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
from agptools.logs import logger
|
|
22
|
+
|
|
23
|
+
log = logger(__name__)
|
|
24
|
+
|
|
25
|
+
# subloger = logger(f'{__name__}.subloger')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =========================================================
|
|
29
|
+
# smartmodels
|
|
30
|
+
# =========================================================
|
|
31
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Asterio Gonzalez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: smartmodels
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Smart Models for FIWARE
|
|
5
|
+
Home-page: https://github.com/asterio/smartmodels
|
|
6
|
+
Author: Asterio Gonzalez
|
|
7
|
+
Author-email: asterio.gonzalez@gmail.com
|
|
8
|
+
License: MIT license
|
|
9
|
+
Keywords: smartmodels
|
|
10
|
+
Platform: UNKNOWN
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Requires-Python: >=3.6
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
License-File: AUTHORS.rst
|
|
22
|
+
Requires-Dist: Click (>=7.1.2)
|
|
23
|
+
Requires-Dist: agptools
|
|
24
|
+
Requires-Dist: glom
|
|
25
|
+
Requires-Dist: jmespath
|
|
26
|
+
|
|
27
|
+
===========
|
|
28
|
+
SmartModels
|
|
29
|
+
===========
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
.. image:: https://img.shields.io/pypi/v/smartmodels.svg
|
|
33
|
+
:target: https://pypi.python.org/pypi/smartmodels
|
|
34
|
+
|
|
35
|
+
.. image:: https://img.shields.io/travis/asterio/smartmodels.svg
|
|
36
|
+
:target: https://travis-ci.com/asterio/smartmodels
|
|
37
|
+
|
|
38
|
+
.. image:: https://readthedocs.org/projects/smartmodels/badge/?version=latest
|
|
39
|
+
:target: https://smartmodels.readthedocs.io/en/latest/?version=latest
|
|
40
|
+
:alt: Documentation Status
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Smart Models for FIWARE
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
* Free software: MIT license
|
|
49
|
+
* Documentation: https://smartmodels.readthedocs.io.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Features
|
|
53
|
+
----------------
|
|
54
|
+
|
|
55
|
+
* TODO
|
|
56
|
+
|
|
57
|
+
Before Coding
|
|
58
|
+
----------------
|
|
59
|
+
|
|
60
|
+
* create a virtual env
|
|
61
|
+
|
|
62
|
+
::
|
|
63
|
+
python3 -m venv venv
|
|
64
|
+
source venv/bin/activate
|
|
65
|
+
make install-testing-requisites
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Credits
|
|
70
|
+
----------------
|
|
71
|
+
|
|
72
|
+
This package was created with Cookiecutter_ and the `cib_pypackage`_ project template.
|
|
73
|
+
|
|
74
|
+
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
|
75
|
+
.. _`cib_pypackage`: https://git.spec-cibernos.com/spec/templates/cib_pypackage
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
=======
|
|
79
|
+
History
|
|
80
|
+
=======
|
|
81
|
+
|
|
82
|
+
0.1.0 (2024-02-07)
|
|
83
|
+
------------------
|
|
84
|
+
|
|
85
|
+
* First release on PyPI.
|
|
86
|
+
|
|
87
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
smartmodels/__init__.py,sha256=VnqJPLI4i4souu-742WRkiubW1wNveWpWb6e3478sec,141
|
|
2
|
+
smartmodels/helpers.py,sha256=n-NWprHyIrqg7s-N6Bh6t05sawbAH-i10C4WAFaN3y4,5708
|
|
3
|
+
smartmodels/smartmodels.py,sha256=75tOPm5n51W8M37lqFcsQZ-2jHmq3V0KTVqMCdvECsU,550
|
|
4
|
+
smartmodels/cli/__init__.py,sha256=HcpquBnPgYbtDEQ1zYCIXXYRVLcMOzW89uS6kpLKFpQ,844
|
|
5
|
+
smartmodels/cli/config.py,sha256=1jNso7I0tNwDnwZu3SsvFMgmCL2nznGZ2z9Lr51iCsE,3711
|
|
6
|
+
smartmodels/cli/main.py,sha256=Cq2Vb-CiTtGT66G10BC7CwUdzeQkiS9SkmcsPFlAC7Y,1565
|
|
7
|
+
smartmodels/cli/setup.py,sha256=lTfh6KY5Md_SqzDMXLafulH0txDMwv85LWU_ATUXwMw,3625
|
|
8
|
+
smartmodels/cli/wingdbstub.py,sha256=3jzRnyg351_Z898pxFfEZvfr93m3msCf4rQYBAKMRjQ,39
|
|
9
|
+
smartmodels/cli/workspace.py,sha256=p0ELSARq77TBED8kuSyFg9h8LZavswWuX03q52gbFbE,3127
|
|
10
|
+
smartmodels-0.1.0.dist-info/AUTHORS.rst,sha256=3ZPoqg8Aav8DSYKd0fwcwn4_5HwSiMLart0E5Un00-U,168
|
|
11
|
+
smartmodels-0.1.0.dist-info/LICENSE,sha256=JqzQ9S4hVjQBOM8SiUG9-qhokt1oKfE2qJMRNa3t410,1075
|
|
12
|
+
smartmodels-0.1.0.dist-info/METADATA,sha256=FYCWynOhbGdaEUxMhm-BI0hjl9yMxh0MYvPoAV4W0z8,1926
|
|
13
|
+
smartmodels-0.1.0.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
|
|
14
|
+
smartmodels-0.1.0.dist-info/entry_points.txt,sha256=kZrQd6boeTe5YikNYC_L22M7UMuoBQeHwD8SF8BEGiI,54
|
|
15
|
+
smartmodels-0.1.0.dist-info/top_level.txt,sha256=OdN-SOwhQYptkH0_ZYMAtsC3UmQ3MhZ9neK26CNLx0A,12
|
|
16
|
+
smartmodels-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
smartmodels
|