forked from dbt-labs/dbt-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathversion.py
239 lines (177 loc) · 7.29 KB
/
version.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import importlib
import importlib.util
import os
import glob
import json
from typing import Iterator, List, Optional, Tuple
import requests
import dbt.exceptions
import dbt.semver
from dbt.ui import green, red, yellow
from dbt import flags
PYPI_VERSION_URL = "https://pypi.org/pypi/dbt-core/json"
def get_version_information() -> str:
flags.USE_COLORS = True if not flags.USE_COLORS else None
installed = get_installed_version()
latest = get_latest_version()
core_msg_lines, core_info_msg = _get_core_msg_lines(installed, latest)
core_msg = _format_core_msg(core_msg_lines)
plugin_version_msg = _get_plugins_msg(installed)
msg_lines = [core_msg]
if core_info_msg != "":
msg_lines.append(core_info_msg)
msg_lines.append(plugin_version_msg)
msg_lines.append("")
return "\n\n".join(msg_lines)
def get_installed_version() -> dbt.semver.VersionSpecifier:
return dbt.semver.VersionSpecifier.from_version_string(__version__)
def get_latest_version(
version_url: str = PYPI_VERSION_URL,
) -> Optional[dbt.semver.VersionSpecifier]:
try:
resp = requests.get(version_url)
data = resp.json()
version_string = data["info"]["version"]
except (json.JSONDecodeError, KeyError, requests.RequestException):
return None
return dbt.semver.VersionSpecifier.from_version_string(version_string)
def _get_core_msg_lines(installed, latest) -> Tuple[List[List[str]], str]:
installed_s = installed.to_version_string(skip_matcher=True)
installed_line = ["installed", installed_s, ""]
update_info = ""
if latest is None:
update_info = (
" The latest version of dbt-core could not be determined!\n"
" Make sure that the following URL is accessible:\n"
f" {PYPI_VERSION_URL}"
)
return [installed_line], update_info
latest_s = latest.to_version_string(skip_matcher=True)
latest_line = ["latest", latest_s, green("Up to date!")]
if installed > latest:
latest_line[2] = green("Ahead of latest version!")
elif installed < latest:
latest_line[2] = yellow("Update available!")
update_info = (
" Your version of dbt-core is out of date!\n"
" You can find instructions for upgrading here:\n"
" https://docs.getdbt.com/docs/installation"
)
return [
installed_line,
latest_line,
], update_info
def _format_core_msg(lines: List[List[str]]) -> str:
msg = "Core:\n"
msg_lines = []
for name, version, update_msg in _pad_lines(lines, seperator=":"):
line_msg = f" - {name} {version}"
if update_msg != "":
line_msg += f" - {update_msg}"
msg_lines.append(line_msg)
return msg + "\n".join(msg_lines)
def _get_plugins_msg(installed: dbt.semver.VersionSpecifier) -> str:
msg_lines = ["Plugins:"]
plugins = []
display_update_msg = False
for name, version_s in _get_dbt_plugins_info():
compatability_msg, needs_update = _get_plugin_msg_info(name, version_s, installed)
if needs_update:
display_update_msg = True
plugins.append([name, version_s, compatability_msg])
for plugin in _pad_lines(plugins, seperator=":"):
msg_lines.append(_format_single_plugin(plugin, ""))
if display_update_msg:
update_msg = (
" At least one plugin is out of date or incompatible with dbt-core.\n"
" You can find instructions for upgrading here:\n"
" https://docs.getdbt.com/docs/installation"
)
msg_lines += ["", update_msg]
return "\n".join(msg_lines)
def _get_plugin_msg_info(
name: str, version_s: str, core: dbt.semver.VersionSpecifier
) -> Tuple[str, bool]:
plugin = dbt.semver.VersionSpecifier.from_version_string(version_s)
latest_plugin = get_latest_version(version_url=get_package_pypi_url(name))
needs_update = False
if plugin.major != core.major or plugin.minor != core.minor:
compatibility_msg = red("Not compatible!")
needs_update = True
return (compatibility_msg, needs_update)
if not latest_plugin:
compatibility_msg = yellow("Could not determine latest version")
return (compatibility_msg, needs_update)
if plugin < latest_plugin:
compatibility_msg = yellow("Update available!")
needs_update = True
elif plugin > latest_plugin:
compatibility_msg = green("Ahead of latest version!")
else:
compatibility_msg = green("Up to date!")
return (compatibility_msg, needs_update)
def _format_single_plugin(plugin: List[str], update_msg: str) -> str:
name, version_s, compatability_msg = plugin
msg = f" - {name} {version_s} - {compatability_msg}"
if update_msg != "":
msg += f"\n{update_msg}\n"
return msg
def _pad_lines(lines: List[List[str]], seperator: str = "") -> List[List[str]]:
if len(lines) == 0:
return []
# count the max line length for each column in the line
counter = [0] * len(lines[0])
for line in lines:
for i, item in enumerate(line):
counter[i] = max(counter[i], len(item))
result: List[List[str]] = []
for i, line in enumerate(lines):
# add another list to hold padded strings
if len(result) == i:
result.append([""] * len(line))
# iterate over columns in the line
for j, item in enumerate(line):
# the last column does not need padding
if j == len(line) - 1:
result[i][j] = item
continue
# if the following column has no length
# the string does not need padding
if counter[j + 1] == 0:
result[i][j] = item
continue
# only add the seperator to the first column
offset = 0
if j == 0 and seperator != "":
item += seperator
offset = len(seperator)
result[i][j] = item.ljust(counter[j] + offset)
return result
def get_package_pypi_url(package_name: str) -> str:
return f"https://pypi.org/pypi/dbt-{package_name}/json"
def _get_dbt_plugins_info() -> Iterator[Tuple[str, str]]:
for plugin_name in _get_adapter_plugin_names():
if plugin_name == "core":
continue
try:
mod = importlib.import_module(f"dbt.adapters.{plugin_name}.__version__")
except ImportError:
# not an adapter
continue
yield plugin_name, mod.version # type: ignore
def _get_adapter_plugin_names() -> Iterator[str]:
spec = importlib.util.find_spec("dbt.adapters")
# If None, then nothing provides an importable 'dbt.adapters', so we will
# not be reporting plugin versions today
if spec is None or spec.submodule_search_locations is None:
return
for adapters_path in spec.submodule_search_locations:
version_glob = os.path.join(adapters_path, "*", "__version__.py")
for version_path in glob.glob(version_glob):
# the path is like .../dbt/adapters/{plugin_name}/__version__.py
# except it could be \\ on windows!
plugin_root, _ = os.path.split(version_path)
_, plugin_name = os.path.split(plugin_root)
yield plugin_name
__version__ = "1.3.0b1"
installed = get_installed_version()