Coretex
utils.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from typing import List, Any, Tuple, Optional, Callable
19 from pathlib import Path
20 from functools import wraps
21 from importlib.metadata import version as getLibraryVersion
22 
23 import sys
24 import venv
25 import shutil
26 import logging
27 import platform
28 
29 from py3nvml import py3nvml
30 
31 import click
32 import requests
33 
34 from . import ui
35 from ...configuration import DEFAULT_VENV_PATH
36 from ...utils.process import command
37 
38 
39 def formatCliVersion(version: Tuple[int, int, int]) -> str:
40  return ".".join(map(str, version))
41 
42 
43 def fetchCtxSource() -> Optional[str]:
44  _, output, _ = command([sys.executable, "-m", "pip", "freeze"], ignoreStdout = True, ignoreStderr = True)
45  packages = output.splitlines()
46 
47  for package in packages:
48  if "coretex" in package:
49  return package.replace(" ", "")
50 
51  return None
52 
53 
54 def createEnvironment(venvPython: Path) -> None:
55  if DEFAULT_VENV_PATH.exists():
56  shutil.rmtree(DEFAULT_VENV_PATH)
57 
58  venv.create(DEFAULT_VENV_PATH, with_pip = True)
59 
60  if platform.system() == "Windows":
61  venvPython = DEFAULT_VENV_PATH / "Scripts" / "python.exe"
62 
63  ctxSource = fetchCtxSource()
64  if ctxSource is not None:
65  command([str(venvPython), "-m", "pip", "install", ctxSource], ignoreStdout = True, ignoreStderr = True)
66 
67 
68 def checkEnvironment() -> None:
69  venvPython = DEFAULT_VENV_PATH / "bin" / "python"
70  venvActivate = DEFAULT_VENV_PATH / "bin" / "activate"
71  venvCoretex = DEFAULT_VENV_PATH / "bin" / "coretex"
72 
73  if not venvActivate.exists() or not venvPython.exists():
74  createEnvironment(venvPython)
75  return
76 
77  try:
78  command([str(venvCoretex), "version"], check = True, ignoreStderr = True, ignoreStdout = True)
79  except Exception:
80  createEnvironment(venvPython)
81  return
82 
83 
84 def updateLib() -> None:
85  command([sys.executable, "-m", "pip", "install", "--no-cache-dir", "--upgrade", "coretex"], ignoreStdout = True, ignoreStderr = True)
86 
87 
88 def parseLibraryVersion(version: str) -> Optional[Tuple[int, int, int]]:
89  parts = version.split(".")
90 
91  if len(parts) != 3:
92  return None
93 
94  if all(part.isdigit() for part in parts):
95  major, minor, patch = map(int, version.split('.'))
96  return major, minor, patch
97 
98  return None
99 
100 
101 def fetchCurrentVersion() -> Optional[Tuple[int, int, int]]:
102  version = parseLibraryVersion(getLibraryVersion("coretex"))
103  if version is None:
104  logging.getLogger("cli").debug(f"Couldn't parse current version from string: {version}")
105  return None
106 
107  return version
108 
109 
110 def fetchLatestVersion() -> Optional[Tuple[int, int, int]]:
111  url = "https://pypi.org/pypi/coretex/json"
112  response = requests.get(url)
113 
114  if not response.ok:
115  logging.getLogger("cli").debug(f"Failed to fetch version of coretex library. Response code: {response.status_code}")
116  return None
117 
118  data = response.json()
119  infoDict = data.get("info")
120  if not isinstance(infoDict, dict):
121  logging.getLogger("cli").debug("Value of json field of key \"info\" in \"data\" dictionary is not of expected type (dict).")
122  return None
123 
124  version = infoDict.get("version")
125  if not isinstance(version, str):
126  logging.getLogger("cli").debug("Value of json field of key \"version\" in \"info\" dictionary is not of expected type (str).")
127  return None
128 
129  parsedVersion = parseLibraryVersion(version)
130  if parsedVersion is None:
131  logging.getLogger("cli").debug(f"Couldn't parse latest version from string: {version}")
132  return None
133 
134  return parsedVersion
135 
136 
137 def checkLibVersion() -> None:
138  currentVersion = fetchCurrentVersion()
139  latestVersion = fetchLatestVersion()
140 
141  if currentVersion is None or latestVersion is None:
142  return
143 
144  if latestVersion > currentVersion:
145  ui.warningEcho(
146  f"Newer version of Coretex library is available. "
147  f"Current: {formatCliVersion(currentVersion)}, Latest: {formatCliVersion(latestVersion)}."
148  )
149  ui.stdEcho("Use \"coretex update\" command to update library to latest version.")
150 
151 
152 def getExecPath(executable: str) -> str:
153  _, path, _ = command(["which", executable], ignoreStdout = True, ignoreStderr = True)
154  pathParts = path.strip().split('/')
155  execPath = '/'.join(pathParts[:-1])
156 
157  return execPath
158 
159 
160 def isGPUAvailable() -> bool:
161  try:
162  py3nvml.nvmlInit()
163  py3nvml.nvmlShutdown()
164  return True
165  except:
166  return False
167 
168 
169 def onBeforeCommandExecute(
170  fun: Callable[..., Any],
171  excludeOptions: Optional[List[str]] = None,
172  excludeSubcommands: Optional[List[str]] = None
173 ) -> Any:
174 
175  if excludeOptions is None:
176  excludeOptions = []
177 
178  if excludeSubcommands is None:
179  excludeSubcommands = []
180 
181  def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
182  @wraps(f)
183  def wrapper(*args: Any, **kwargs: Any) -> Any:
184  if click.get_current_context().invoked_subcommand in excludeSubcommands:
185  return f(*args, **kwargs)
186 
187  for key, value in click.get_current_context().params.items():
188  if key in excludeOptions and value:
189  return f(*args, **kwargs)
190 
191  fun()
192  return f(*args, **kwargs)
193  return wrapper
194  return decorator