ayon-core/igniter/tools.py

236 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
"""Tools used in **Igniter** GUI.
Functions ``compose_url()`` and ``decompose_url()`` are the same as in
``pype.lib`` and they are here to avoid importing pype module before its
version is decided.
"""
from typing import Dict, Union
from urllib.parse import urlparse, parse_qs
from pathlib import Path
import platform
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError, InvalidURI
def decompose_url(url: str) -> Dict:
"""Decompose mongodb url to its separate components.
Args:
url (str): Mongodb url.
Returns:
dict: Dictionary of components.
"""
components = {
"scheme": None,
"host": None,
"port": None,
"username": None,
"password": None,
"auth_db": None
}
result = urlparse(url)
if result.scheme is None:
_url = "mongodb://{}".format(url)
result = urlparse(_url)
components["scheme"] = result.scheme
components["host"] = result.hostname
try:
components["port"] = result.port
except ValueError:
raise RuntimeError("invalid port specified")
components["username"] = result.username
components["password"] = result.password
try:
components["auth_db"] = parse_qs(result.query)['authSource'][0]
except KeyError:
# no auth db provided, mongo will use the one we are connecting to
pass
return components
def compose_url(scheme: str = None,
host: str = None,
username: str = None,
password: str = None,
port: int = None,
auth_db: str = None) -> str:
"""Compose mongodb url from its individual components.
Args:
scheme (str, optional):
host (str, optional):
username (str, optional):
password (str, optional):
port (str, optional):
auth_db (str, optional):
Returns:
str: mongodb url
"""
url = "{scheme}://"
if username and password:
url += "{username}:{password}@"
url += "{host}"
if port:
url += ":{port}"
if auth_db:
url += "?authSource={auth_db}"
return url.format(**{
"scheme": scheme,
"host": host,
"username": username,
"password": password,
"port": port,
"auth_db": auth_db
})
def validate_mongo_connection(cnx: str) -> (bool, str):
"""Check if provided mongodb URL is valid.
Args:
cnx (str): URL to validate.
Returns:
(bool, str): True if ok, False if not and reason in str.
"""
parsed = urlparse(cnx)
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
return False, "Not mongodb schema"
# we have mongo connection string. Let's try if we can connect.
try:
components = decompose_url(cnx)
except RuntimeError:
return False, f"Invalid port specified."
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 2000
}
port = components.get("port")
if port is not None:
mongo_args["port"] = int(port)
try:
client = MongoClient(**mongo_args)
client.server_info()
except ServerSelectionTimeoutError as e:
return False, f"Cannot connect to server {cnx} - {e}"
except ValueError:
return False, f"Invalid port specified {parsed.port}"
except InvalidURI as e:
return False, str(e)
else:
return True, "Connection is successful"
def validate_mongo_string(mongo: str) -> (bool, str):
"""Validate string if it is mongo url acceptable by **Igniter**..
Args:
mongo (str): String to validate.
Returns:
(bool, str):
True if valid, False if not and in second part of tuple
the reason why it failed.
"""
if not mongo:
return True, "empty string"
parsed = urlparse(mongo)
if parsed.scheme in ["mongodb", "mongodb+srv"]:
return validate_mongo_connection(mongo)
return False, "not valid mongodb schema"
def validate_path_string(path: str) -> (bool, str):
"""Validate string if it is path to Pype repository.
Args:
path (str): Path to validate.
Returns:
(bool, str):
True if valid, False if not and in second part of tuple
the reason why it failed.
"""
if not path:
return False, "empty string"
if not Path(path).exists():
return False, "path doesn't exists"
if not Path(path).is_dir():
return False, "path is not directory"
return True, "valid path"
def get_pype_global_settings(url: str) -> dict:
"""Load global settings from Mongo database.
We are loading data from database `pype` and collection `settings`.
There we expect document type `global_settings`.
Returns:
dict: With settings data. Empty dictionary is returned if not found.
"""
try:
components = decompose_url(url)
except RuntimeError:
return {}
mongo_kwargs = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 2000
}
port = components.get("port")
if port is not None:
mongo_kwargs["port"] = int(port)
try:
# Create mongo connection
client = MongoClient(**mongo_kwargs)
# Access settings collection
col = client["pype"]["settings"]
# Query global settings
global_settings = col.find_one({"type": "global_settings"}) or {}
# Close Mongo connection
client.close()
except Exception:
# TODO log traceback or message
return {}
return global_settings.get("data") or {}
def get_pype_path_from_db(url: str) -> Union[str, None]:
"""Get Pype path from global settings.
Args:
url (str): mongodb url.
Returns:
path to Pype or None if not found
"""
global_settings = get_pype_global_settings(url)
return global_settings.get("pype_path", {}).get(platform.system().lower())