Skip to content

Commit

Permalink
Refactor config tests and correct setup classifier spelling
Browse files Browse the repository at this point in the history
  • Loading branch information
Struan Judd committed Mar 23, 2018
1 parent a3594ca commit e26de74
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 148 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/Struan.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/runConfigurations/py_test_script_venv.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import sys
sys.path.insert(0, os.path.abspath('..'))

import script_venv # noqa: E402
# import script_venv # noqa: E402

# -- General configuration ---------------------------------------------

Expand Down Expand Up @@ -56,9 +56,9 @@
# the built documents.
#
# The short X.Y version.
version = script_venv.__version__
# version = script_venv.__version__
# The full version, including alpha/beta/rc tags.
release = script_venv.__version__
# release = script_venv.__version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
1 change: 0 additions & 1 deletion script_venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@

__author__ = """Struan Lyall Judd"""
__email__ = '[email protected]'
__version__ = '0.1.0'
13 changes: 8 additions & 5 deletions script_venv/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import click
import sys
from typing import Dict, Set, Iterable # noqa: F401
from typing import Iterable # noqa: F401

from .config import VenvConfig
from .factory import ConfigDependenciesImpl
from .script_venv import ScriptVenvGroup


Expand All @@ -29,7 +30,7 @@ def register_package(venv: str,
is_local: bool,
is_global: bool) -> int: # pragma: no cover
"""Register packages and their scripts in venv"""
config = VenvConfig()
config = VenvConfig(deps=ConfigDependenciesImpl())
config.register(venv, package, per_user, is_local if per_user else not is_global)
return 0

Expand All @@ -38,10 +39,12 @@ def register_package(venv: str,
@click.option('--clean', '-c', is_flag=True, help='If the venv exists, clean it before applying requirements')
@click.argument('venv_or_script', required=True)
@click.argument('install_params', nargs=-1)
def create_venv(venv_or_script: str, install_params: Iterable[str], clean: bool):
def create_venv(venv_or_script: str,
install_params: Iterable[str],
clean: bool) -> None: # pragma: no cover
"""Create or clean venv and apply requirements
appending any install parameters provided"""
config = VenvConfig()
config = VenvConfig(deps=ConfigDependenciesImpl())
config.load(False)
config.load(True)
config.create(venv_or_script, *install_params, clean=clean)
Expand All @@ -50,7 +53,7 @@ def create_venv(venv_or_script: str, install_params: Iterable[str], clean: bool)
@main.command(name=":list")
def list_venvs() -> None:
"""List known scripts and venvs"""
config = VenvConfig()
config = VenvConfig(deps=ConfigDependenciesImpl())
config.load(False)
config.load(True)
config.list()
Expand Down
93 changes: 46 additions & 47 deletions script_venv/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from os import path
from pathlib import Path
from types import MappingProxyType
from typing import Mapping, Set, Dict, Iterable, Tuple # noqa: F401
from typing import Mapping, Set, Dict, Iterable, Tuple, Any, IO # noqa: F401

from .venv import VEnv

Expand All @@ -24,14 +24,30 @@
pipdeptree
"""

_S = "SCRIPTS"
_s = "SCRIPTS"
_p = "prerequisites"
_r = "requirements"
_l = "local"
_g = "global"


class ConfigDependencies(object): # pragma: no cover
def exists(self, path: Path) -> bool:
raise NotImplementedError()

def read(self, path: Path) -> IO[Any]:
raise NotImplementedError()

def scripts(self, venv: VEnv, packages: Iterable[str]) -> Iterable[Tuple[str, str]]:
raise NotImplementedError()

def write(self, config: ConfigParser, path: Path):
raise NotImplementedError()


class VenvConfig(object):
def __init__(self) -> None:
def __init__(self, deps: ConfigDependencies) -> None:
self.deps = deps
self.configs = set() # type: Set[str]
self._scripts = {} # type: Dict[str, str]
self._venvs = {} # type: Dict[str, VEnv]
Expand All @@ -44,20 +60,20 @@ def _file_path(per_user: bool) -> Tuple[str, Path]:
return config_file.as_posix(), Path(path.expanduser(str(config_file))).absolute()

@staticmethod
def _requirements(config: ConfigParser, venv: str) -> Set[str]:
value = config.get(venv, _r, fallback=None) or ''
def _packages_section(config: ConfigParser, venv: str, section: str) -> Set[str]:
value = config.get(venv, section, fallback=None) or ''
return {r for r in value.splitlines() if r}

def load(self, per_user: bool, in_file: StringIO = None) -> None:
def load(self, per_user: bool) -> None:
config_file, config_file_path = self._file_path(per_user)

if not in_file and not config_file_path.exists(): # pragma: no cover
if not self.deps.exists(config_file_path): # pragma: no cover
return

self.configs.add(config_file)

config = ConfigParser(allow_no_value=True)
with in_file or config_file_path.open() as in_config:
with self.deps.read(config_file_path) as in_config:
config.read_file(in_config)

for v in config:
Expand All @@ -66,17 +82,20 @@ def load(self, per_user: bool, in_file: StringIO = None) -> None:
is_local = config.has_option(v, _l)
else:
is_local = not config.has_option(v, _g)
req = self._requirements(config, v)
self._venvs.setdefault(v, VEnv(v, local=is_local, requirements=req))

if config.has_section(_S):
scripts = config[_S]
self._venvs.setdefault(v, VEnv(v, local=is_local,
requirements=self._packages_section(config, v, _r),
prerequisites=self._packages_section(config, v, _p),
))

if config.has_section(_s):
scripts = config[_s]
for s in scripts:
v = scripts[s] or s
self._scripts[s] = v
self._venvs.setdefault(v, VEnv(v, local=per_user))

ignored = [s for s in config.sections() if not (s.islower() or s == _S)]
ignored = [s for s in config.sections() if not (s.islower() or s == _s)]

if ignored:
echo("Ignored the following sections of %s: %s" % (config_file, ', '.join(sorted(ignored))))
Expand All @@ -102,61 +121,41 @@ def scripts(self) -> Mapping[str, str]:
def venvs(self) -> Mapping[str, VEnv]:
return self._venvs_proxy

def register(self, name: str, packages: Iterable[str], per_user: bool, is_local: bool,
out_file: StringIO=None, package_scripts=None):
def register(self, name: str, packages: Iterable[str],
per_user: bool, is_local: bool,
out_file: StringIO=None) -> None:
config_file, config_file_path = self._file_path(per_user)

config = ConfigParser(allow_no_value=True)
if config_file_path.exists():
with config_file_path.open() as in_file:
if self.deps.exists(config_file_path):
with self.deps.read(config_file_path) as in_file:
config.read_file(in_file)

if not config.has_section(_S):
config.add_section(_S)
requirements = self._requirements(config, name)

if not package_scripts: # pragma: no cover
venv = VEnv(name, local=is_local)
venv.requirements = requirements
if venv.create():
venv.install(*requirements)
venv.install(*packages)

try:
import pkg_resources
except ImportError:
echo("Unable to import pkg_resources to register %s into %s" % (sorted(packages), venv))
return

pkg_env = pkg_resources.Environment(search_path=[str(venv.abs_path / 'lib' / 'site-packages')])

def pkg_scripts(p: str) -> Iterable[str]:
scripts = {} # type: Dict
dist = pkg_env[p]
if len(dist):
scripts = dist[0].get_entry_map().get('console_scripts') or {}
return scripts.keys()
if not config.has_section(_s):
config.add_section(_s)
requirements = self._packages_section(config, name, _r)

package_scripts = pkg_scripts
venv = VEnv(name, local=is_local, requirements=requirements,
prerequisites=self._packages_section(config, name, _p))

for p in packages:
for s in package_scripts(p):
for p, s in self.deps.scripts(venv, packages):
echo("Registering %s from %s into %s" % (s, p, name))
config.set(_S, s, name)
config.set(_s, s, name)

if not config.has_section(name):
config.add_section(name)
config.set(name, _r, '\n'.join(sorted(requirements | set(packages))))
if per_user == is_local:
config.set(name, _l if per_user else _g, None)

self.deps.write(config, config_file_path)
if out_file:
config.write(out_file)
else:
with config_file_path.open('w') as out_config:
config.write(out_config)

def create(self, venv_or_script: str, *extra_params: str, clean: bool=False):
def create(self, venv_or_script: str, *extra_params: str, clean: bool=False) -> None:
if venv_or_script in self._venvs:
venv = self._venvs[venv_or_script]
elif venv_or_script in self._scripts:
Expand Down
45 changes: 45 additions & 0 deletions script_venv/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-

""" Config file processing """

from click import echo
from configparser import ConfigParser
from pathlib import Path
from typing import Iterable, Tuple, Dict, Any, IO # noqa: F401

from .config import ConfigDependencies
from .venv import VEnv


class ConfigDependenciesImpl(ConfigDependencies):
def write(self, config: ConfigParser, path: Path): # pragma: no cover
with path.open('w') as out_config:
config.write(out_config)

def scripts(self, venv: VEnv, packages: Iterable[str]) -> Iterable[Tuple[str, str]]: # pragma: no cover
if venv.create():
venv.install(*venv.requirements)
venv.install(*packages)

try:
import pkg_resources
except ImportError:
echo("Unable to import pkg_resources to register %s into %s" % (sorted(packages), venv))
return []

pkg_env = pkg_resources.Environment(search_path=[str(venv.abs_path / 'lib' / 'site-packages')])

def pkg_scripts(p: str) -> Iterable[str]:
scripts = {} # type: Dict
dist = pkg_env[p]
if len(dist):
scripts = dist[0].get_entry_map().get('console_scripts') or {}
return scripts.keys()

return ((s, p) for p in packages for s in pkg_scripts(p))

def read(self, path: Path) -> IO[Any]:
return path.open()

def exists(self, path: Path) -> bool:
return path.exists()
3 changes: 2 additions & 1 deletion script_venv/script_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from typing import Iterable, Any

from .config import VenvConfig
from .factory import ConfigDependenciesImpl


class ScriptVenvContext(Context):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super(ScriptVenvContext, self). __init__(*args, **kwargs)
self.config = VenvConfig()
self.config = VenvConfig(deps=ConfigDependenciesImpl())


class ScriptVenvCommand(Command):
Expand Down
8 changes: 7 additions & 1 deletion script_venv/venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@


class VEnv(object):
def __init__(self, name, requirements: Iterable[str] = None, local=False) -> None:
def __init__(self, name,
requirements: Iterable[str] = None,
prerequisites: Iterable[str] = None,
local=False) -> None:
self.name = name
self.requirements = set(requirements or [])
self.prerequisites = set(prerequisites or [])
self.env_path = str((Path('.sv') if local else Path('~') / '.sv') / name)
self.abs_path = Path(os.path.expanduser(self.env_path)).absolute()

Expand Down Expand Up @@ -74,4 +78,6 @@ def create(self, clean=False, creator=None) -> bool:
creator = creator if callable(creator) else venv.create

creator(str(self.abs_path), with_pip=True, clear=clean)
if self.prerequisites:
self.install(*self.prerequisites)
return True
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
author="Struan Lyall Judd",
author_email='[email protected]',
classifiers=[
'Development Status :: 4 - Betaa',
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
Expand Down
6 changes: 6 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-

"""Tests package for Script Venv."""

__author__ = """Struan Lyall Judd"""
__email__ = '[email protected]'
30 changes: 30 additions & 0 deletions tests/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-

""" Doubles for testing"""
from configparser import ConfigParser
from io import StringIO
from pathlib import Path
from typing import IO, Any, Iterable, Tuple

from script_venv.config import ConfigDependencies
from script_venv.venv import VEnv


class TestConfigDependencies(ConfigDependencies):
in_str = ""
out_str = ""

def exists(self, path: Path) -> bool:
return bool(self.in_str)

def read(self, path: Path) -> IO[Any]:
# noinspection PyTypeChecker
return StringIO(self.in_str)

def scripts(self, venv: VEnv, packages: Iterable[str]) -> Iterable[Tuple[str, str]]:
return [(p, p + ".script") for p in packages]

def write(self, config: ConfigParser, path: Path):
with StringIO() as out_file:
config.write(out_file)
self.out_str = out_file.getvalue()
Loading

0 comments on commit e26de74

Please sign in to comment.