"""Paraphernalia is a collection of tools for making digital art."""
import logging
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional
from paraphernalia._project import Project, project
from paraphernalia._random import get_seed, set_seed
from paraphernalia._settings import Settings, settings
# TODO: Shift to poetry-version-plugin, once that's bedded in?
try:
import importlib.metadata as importlib_metadata
except ModuleNotFoundError:
import importlib_metadata # type: ignore
_LOG = logging.getLogger(__name__)
__version__ = importlib_metadata.version(__name__)
_BANNER = f"""
.-. .-. _
: : : : :_;
.---. .--. .--. .--. .---.: `-. .--..--.,-.,-..--. : : .-..--.
: .; ' .; ;: ..' .; ;: .; : .. ' '_.: ..: ,. ' .; ;: :_: ' .; ;
: ._.`.__,_:_; `.__,_: ._.:_;:_`.__.:_; :_;:_`.__,_`.__:_`.__,_;
: : : :
:_; :_; v{__version__}
"""
__all__ = [
"setup",
"setup_logging",
"get_seed",
"set_seed",
"settings",
"project",
"Settings",
"Project",
]
[docs]def setup() -> None:
"""
Set up the library for interactive use by:
- Configuring logging
- Printing a vanity banner and some system information
- (If running in Colaboratory) calling :func:`setup_colab`
"""
setup_logging()
if running_in_colab():
setup_colab()
setup_banner()
[docs]def setup_logging(use_rich: Optional[bool] = None) -> None:
"""
Setup basic logging.
Args:
use_rich (bool, optional): use the pretty rich log handler if available.
Defaults to value in settings.
"""
handlers = None
fmt = "%(asctime)s %(levelname)s %(name)s : %(message)s"
use_rich = settings().use_rich if use_rich is None else use_rich
if use_rich:
try:
import rich
from rich.logging import RichHandler
rich.get_console().width = 120 # Default is 93, which is too small
fmt = "%(message)s"
handlers = [RichHandler(rich_tracebacks=True)]
except ImportError:
pass
logging.basicConfig(
level=logging.INFO, format=fmt, datefmt="[%X]", handlers=handlers, force=True
)
def setup_banner():
"""Log a banner and some system information."""
_LOG.info(_BANNER, extra={"highlighter": None})
python_version = sys.version.replace("\n", " ")
_LOG.info(f" Python: {python_version}")
_LOG.info(f" GPU: {get_gpu_name()} (CUDA: {get_cuda_version()})")
_LOG.info(f" Seed: {get_seed()}")
_LOG.info(f" Creator: {settings().creator}")
_LOG.info(f"Projects: {settings().project_home}")
def setup_colab(): # pragma: no cover
"""
Standard setup for Colaboratory:
- Adds the ``data_table`` and and ``tensorboard`` extensions
- Ensures Google drive is mounted under `/content/drive`
- Configures :func:`project_home` to use it
"""
# Load extensions
from IPython import get_ipython
get_ipython().magic("load_ext google.colab.data_table")
get_ipython().magic("load_ext tensorboard")
# Ensure drive is mounted
from google.colab import drive # type: ignore
drive_mount_point = Path("/content/drive")
if drive_mount_point.exists():
_LOG.info(f"Assuming Google Drive already mounted at {drive_mount_point}")
else:
drive.mount(str(drive_mount_point)) # NB Raises an error if user declines
# Use it as project dir
settings().project_home = drive_mount_point / "MyDrive" / "Paraphernalia"
def running_in_colab():
"""
True if running in Colaboratory.
See:
- https://stackoverflow.com/questions/53581278/test-if-notebook-is-running-on-google-colab
Returns:
bool: True if running in Colaboratory
""" # noqa
try:
from IPython import get_ipython
except ImportError:
return False
return "google.colab" in str(get_ipython())
def running_in_github_action() -> bool:
"""
True if running as Github Action.
Returns:
bool: True if running in a Github action
"""
return bool(os.environ.get("GITHUB_ACTIONS", False))
def running_in_jupyter() -> bool:
"""
True if running within Jupyter, and *not*:
- A random python program
- A console-based IPython shell
- VSCode's interactive mode
Returns:
bool: True iff running in Jupyter
"""
try:
from IPython import get_ipython
except ImportError:
return False
ip = get_ipython()
# No species of iPython
if not ip:
return False
# Console
elif "IPKernelApp" not in get_ipython().config: # pragma: no cover
return False
# VS Code
elif "VSCODE_PID" in os.environ: # pragma: no cover
return False
else: # pragma: no cover
return True
def get_cuda_version() -> Optional[str]:
"""
Returns:
the CUDA/nvcc version string e.g. 10.0, or None
"""
try:
return [
s
for s in subprocess.check_output(["nvcc", "--version"])
.decode("UTF-8")
.split(", ")
if s.startswith("release")
][0].split(" ")[-1]
except Exception:
return None
def get_gpu_name() -> Optional[str]:
"""
Returns:
the name of the GPU if available, or None.
"""
try:
import torch
return torch.cuda.get_device_name()
except (ImportError, RuntimeError):
return None
if settings().auto_setup:
setup()