# -*- coding: utf-8 -*-
"""
:summary: Profile and options handling.
:author: francis.horsman@gmail.com
"""
import os
import pprint
from abc import ABCMeta
from functools import wraps
import six
from .defaults import get_opts_key, OptionsKey, DEFAULT_ENV, DEFAULT_VERBOSE, \
DEFAULT_PUBLISH_DOCS_PYPI, DEFAULT_PUBLISH_DOCS_RTD, \
DEFAULT_PUBLISH_PYPI, DEFAULT_GIT_REPO, DEFAULT_GIT_PROFILE, \
DEFAULT_BUILD_RELEASE, DEFAULT_BUILD_RELEASE_DOCS, DEFAULT_TAG_RELEASE, \
DEFAULT_PUSH_RELEASE, DEFAULT_PYPIRC, DEFAULT_HISTORY_FILE, \
DEFAULT_HISTORY_DELIMITER, DEFAULT_HISTORY_DATE, DEFAULT_HISTORY_VERSION, \
DEFAULT_HISTORY_META, DEFAULT_HISTORY_FORMAT, DEFAULT_EXPORT_OPTIONS, \
DEFAULT_BUILD_TEST, DEFAULT_PROJECT_NAME, DEFAULT_GITRC, \
DEFAULT_GIT_PROPAGATE_ENV, DEFAULT_PYTHON_PROPAGATE_ENV, \
DEFAULT_HISTORY_COMMENT, DEFAULT_INTERACTIVE_PROMPT, DEFAULT_INTERACTIVE, \
DEFAULT_LOG_FILE, DEFAULT_LOG_LEVEL, DEFAULT_LOG_MODE
from .errors import NoProfileFile, ProfileLoadError, ProfileConfigurationError
from .utils import update_dict, iExporter
_SUPPORTED_PROFILES = dict()
class ProfileRegistry(type):
def __new__(mcs, name, bases, dct):
if name != '_Profile':
attr_name = dct.get('NAME', None)
if not bases == (object,) and not attr_name:
raise ProfileConfigurationError(
'%s is not configured correctly, it must specify a'
'NAME class attribute.' % name)
del dct['NAME']
properties = dct.get('PROPERTIES')
def _getter(key, default):
@wraps(_getter)
def func(self):
return self.get(key, default)
return func
for prop_options_key, prop_options_default in properties:
prop_name = get_opts_key(prop_options_key)
prop = property(_getter(prop_name, prop_options_default))
dct[prop_name] = prop
def get_args():
@wraps(get_args)
def _inner(**kwargs):
return {
get_opts_key(key): kwargs.get(get_opts_key(key),
default)
for key, default in properties}
return _inner
dct['get_args'] = staticmethod(get_args())
del dct['PROPERTIES']
cls = super(ProfileRegistry, mcs).__new__(mcs, name, bases, dct)
if name != '_Profile':
_SUPPORTED_PROFILES[attr_name] = cls
return cls
@six.add_metaclass(ProfileRegistry)
@six.add_metaclass(ABCMeta)
class _Profile(dict):
pass
class HistorianProfile(_Profile):
NAME = 'history'
PROPERTIES = [(OptionsKey.HISTORY_FILE, DEFAULT_HISTORY_FILE),
(OptionsKey.HISTORY_VERSION, DEFAULT_HISTORY_VERSION),
(OptionsKey.HISTORY_DELIMITER, DEFAULT_HISTORY_DELIMITER),
(OptionsKey.HISTORY_DATE, DEFAULT_HISTORY_DATE),
(OptionsKey.HISTORY_META, DEFAULT_HISTORY_META),
(OptionsKey.HISTORY_FORMAT, DEFAULT_HISTORY_FORMAT),
(OptionsKey.HISTORY_COMMENT, DEFAULT_HISTORY_COMMENT)]
class PublisherProfile(_Profile):
NAME = 'publish'
PROPERTIES = [
(OptionsKey.PUBLISH_RELEASE_DOCS_PYPI, DEFAULT_PUBLISH_DOCS_PYPI),
(OptionsKey.PUBLISH_RELEASE_DOCS_RTD, DEFAULT_PUBLISH_DOCS_RTD),
(OptionsKey.PUBLISH_RELEASE_PYPI, DEFAULT_PUBLISH_PYPI)]
class BuilderProfile(_Profile):
NAME = 'build'
PROPERTIES = [(OptionsKey.BUILD_RELEASE_DOCS, DEFAULT_BUILD_RELEASE_DOCS),
(OptionsKey.BUILD_RELEASE, DEFAULT_BUILD_RELEASE),
(OptionsKey.BUILD_TEST, DEFAULT_BUILD_TEST),
(OptionsKey.GIT_PROPAGATE_ENV, DEFAULT_GIT_PROPAGATE_ENV),
(OptionsKey.PYTHON_PROPAGATE_ENV,
DEFAULT_PYTHON_PROPAGATE_ENV),
(OptionsKey.PYPIRC, DEFAULT_PYPIRC)]
@property
def home(self):
return os.path.dirname(os.path.realpath(self.pypirc))
class VcsProfile(_Profile):
NAME = 'vcs'
PROPERTIES = [(OptionsKey.GIT_PROFILE, DEFAULT_GIT_PROFILE),
(OptionsKey.GITRC, DEFAULT_GITRC),
(OptionsKey.GIT_REPO, DEFAULT_GIT_REPO),
(OptionsKey.TAG_RELEASE, DEFAULT_TAG_RELEASE),
(OptionsKey.PUSH_RELEASE, DEFAULT_PUSH_RELEASE)]
@property
def home(self):
return os.path.dirname(os.path.realpath(self.gitrc))
class MiscProfile(_Profile):
NAME = 'misc'
PROPERTIES = [(OptionsKey.DRY_RUN, DEFAULT_GIT_REPO),
(OptionsKey.PROJECT_NAME, DEFAULT_PROJECT_NAME),
(OptionsKey.INTERACTIVE, DEFAULT_INTERACTIVE),
(OptionsKey.INTERACTIVE_PROMPT, DEFAULT_INTERACTIVE_PROMPT),
(OptionsKey.GENERATE_RCFILE, DEFAULT_EXPORT_OPTIONS)]
class MonitorProfile(_Profile):
NAME = 'monitor'
PROPERTIES = [(OptionsKey.VERBOSE, DEFAULT_VERBOSE),
(OptionsKey.LOG_FILE, DEFAULT_LOG_FILE),
(OptionsKey.LOG_MODE, DEFAULT_LOG_MODE),
(OptionsKey.LOG_LEVEL, DEFAULT_LOG_LEVEL)]
[docs]class Profile(iExporter):
"""
Profile class to allow easy inspection of the profile sections.
"""
def __init__(self):
self._profile_names = self._create_profiles()
def _create_profiles(self):
profiles_names = _SUPPORTED_PROFILES.keys()
for name in profiles_names:
cls = _SUPPORTED_PROFILES[name]
setattr(self, name, cls())
return profiles_names
def __str__(self):
return 'Profile(%s): %s' % self.dry_run
@property
def _export_data(self):
result = {}
for name in self._profile_names:
d = {}
d.update(getattr(self, name))
result[name] = d
return result
def _update_data(self, d):
"""
Update (freshen) from exported_data
"""
for name in self._profile_names:
d[name] = getattr(self, name).update(d.get(name, dict()))
return self
@staticmethod
[docs] def build(d):
"""
Build (overwrite) a new class from exported_data.
"""
return Profile()._update_data(d)
@staticmethod
[docs] def create(d):
"""
Create a new class from a single-depth dict (ie: options).
"""
result = {name: cls.get_args(**d) for name, cls in
_SUPPORTED_PROFILES.iteritems()}
return Profile()._update_data(result)
@property
def dump(self):
return pprint.pformat(self._export_data)
class NewProfile(object):
@staticmethod
def _validate_profile(d):
return d
@staticmethod
def _load_profile(exporter, profile_path, silent=False):
if not profile_path:
return dict()
if not os.path.exists(profile_path):
if not silent:
raise NoProfileFile(
'profile path does not exist: %s' % profile_path)
return dict()
try:
return exporter.loads(open(profile_path), guess=True)
except Exception as err:
raise ProfileLoadError(err,
'Error parsing profile file (is it correct '
'format?): %s' % profile_path)
@staticmethod
def _update_profile(profile, kwargs):
return update_dict(profile, kwargs)
@staticmethod
def _load_profile_from_env(env):
return os.environ.get(env, None)
@staticmethod
def _get_profile_file(profile, env=DEFAULT_ENV):
# Load from env first:
profile_ = NewProfile._load_profile_from_env(env)
# Use cmd-line profile otherwise:
return profile_ if profile_ is not None else profile
@staticmethod
def build(releaser, **kwargs):
"""
Factory method to do all the work of creating the profile dictionary
from all provided sources.
:param kwargs: kwargs to use / include in profile.
:return: dict(profile)
:ProfileLoadError - error in yaml loading (file exists but cannot be
parsed).
"""
profile = kwargs.get('profile', None)
profile_env = kwargs.get('ENV')
profile_file = kwargs.get('profile',
NewProfile._get_profile_file(profile,
profile_env))
loaded_profile = NewProfile._load_profile(
releaser.exporter, profile_file, silent=True)
validated_profile = NewProfile._validate_profile(loaded_profile)
updated_profile = NewProfile._update_profile(validated_profile, kwargs)
return Profile.create(updated_profile)
if __name__ == '__main__': # pragma no cover
pass