1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
3from __future__ import division
4from __future__ import print_function
5from __future__ import unicode_literals
6
7import os
8import io
9import sys
10import six
11import json
12import requests
13from copy import deepcopy
14from importlib import import_module
15from . import exceptions
16from . import config
17
18
19# Retrieve descriptor
20
21def retrieve_descriptor(source):
22
23    try:
24        # Inline
25        if isinstance(source, (dict, list)):
26            return deepcopy(source)
27
28        # String
29        if isinstance(source, six.string_types):
30            # Remote
31            if six.moves.urllib.parse.urlparse(source).scheme in config.REMOTE_SCHEMES:
32                return requests.get(source).json()
33
34            # Local
35            with io.open(source, encoding='utf-8') as file:
36                return json.load(file)
37
38        # Stream
39        return json.load(source)
40
41    except Exception:
42        raise exceptions.LoadError('Can\'t load descriptor')
43
44
45# Expand descriptor
46
47def expand_schema_descriptor(descriptor):
48    if isinstance(descriptor, dict):
49        descriptor = deepcopy(descriptor)
50        for field in descriptor.get('fields', []):
51            field.setdefault('type', config.DEFAULT_FIELD_TYPE)
52            field.setdefault('format', config.DEFAULT_FIELD_FORMAT)
53        descriptor.setdefault('missingValues', config.DEFAULT_MISSING_VALUES)
54    return descriptor
55
56
57def expand_field_descriptor(descriptor):
58    descriptor = deepcopy(descriptor)
59    descriptor.setdefault('type', config.DEFAULT_FIELD_TYPE)
60    descriptor.setdefault('format', config.DEFAULT_FIELD_FORMAT)
61    return descriptor
62
63
64# Miscellaneous
65
66def ensure_dir(path):
67    """Ensure directory exists.
68
69    Args:
70        path(str): dir path
71
72    """
73    dirpath = os.path.dirname(path)
74    if dirpath and not os.path.exists(dirpath):
75        os.makedirs(dirpath)
76
77
78def normalize_value(value):
79    """Convert value to string and make it lower cased.
80    """
81    cast = str
82    if six.PY2:
83        cast = unicode  # noqa
84    return cast(value).lower()
85
86
87def default_exc_handler(exc, *args, **kwargs):
88    """Default exception handler function: raise exc, ignore other arguments.
89    """
90    raise exc
91
92
93class PluginImporter(object):
94    """Plugin importer.
95
96    Example:
97        Add to myapp.plugins something like this:
98        ```
99        importer = PluginImporter(virtual='myapp.plugins.', actual='myapp_')
100        importer.register()
101        del PluginImporter
102        del importer
103        ```
104
105    """
106
107    # Public
108
109    def __init__(self, virtual, actual):
110        self.__virtual = virtual
111        self.__actual = actual
112
113    def __eq__(self, other):
114        if not isinstance(other, type(self)):
115            return False
116        return (self.virtual == other.virtual and
117                self.actual == other.actual)
118
119    @property
120    def virtual(self):
121        return self.__virtual
122
123    @property
124    def actual(self):
125        return self.__actual
126
127    def register(self):
128        if self not in sys.meta_path:
129            sys.meta_path.append(self)
130
131    def find_module(self, fullname, path=None):
132        if fullname.startswith(self.virtual):
133            return self
134        return None
135
136    def load_module(self, fullname):
137        if fullname in sys.modules:
138            return sys.modules[fullname]
139        if not fullname.startswith(self.virtual):
140            raise ImportError(fullname)
141        realname = fullname.replace(self.virtual, self.actual)
142        try:
143            module = import_module(realname)
144        except ImportError:
145            message = 'Plugin "%s" is not installed. '
146            message += 'Run "pip install %s" to install.'
147            message = message % (fullname, realname.replace('_', '-'))
148            raise ImportError(message)
149        sys.modules[realname] = module
150        sys.modules[fullname] = module
151        return module
152