<commit_before>"""
Utilites for accessing the Captricity APIs

NOTE: Methods which start with an underscore (_) are for internal use only and WILL change.
      Do not write your code against them.

"""
import new
import types
import random
import urllib
import httplib
import urlparse
import traceback
import mimetypes
import json
from itertools import groupby
from hashlib import sha256
from urllib import urlencode
from datetime import datetime

API_TOKEN_HEADER_NAME = 'X_API_TOKEN'
API_VERSION_HEADER_NAME = 'X_API_VERSION'
CLIENT_VERSION = '0.01'
USER_AGENT = 'Captricity Python Client %s' % CLIENT_VERSION

class Client(object):
    """
    A network client for the Captricity third-party API.
    This class will fetch the API description from the endpoint URL and dynamically create methods for accessing the API.
    So, once you instantiate the Client you will be able to call cliend.read_job() even though you won't see that method defined below.

    To see a list of all of the api related methods that this client offers, do the following:
    client = Client('your api token', 'http://host/api/backbone/schreddr')
    client.print_help()
    """
    def __init__(self, api_token, endpoint="https://shreddr.captricity.com/api/backbone/schema"):
        """
        endpoint must be the full url to the API schema document, like `http://127.0.0.1:8000/api/backbone/schema'
        api_token is the unique string associated with your account's profile which allows API access
        """
        self.api_token = api_token
        self.endpoint = endpoint
        self.parsed_endpoint = urlparse.urlparse(self.endpoint)
        self.api_version = None
        self.schema = self._getJSON(self.parsed_endpoint.path, version=None)
        self.api_version = self.schema['version']

        for resource in self.schema['resources']:
            if "GET" in resource['allowed_request_methods']:
                read_callable = _generate_read_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'])
                setattr(self, read_callable.__name__, new.instancemethod(read_callable, self, self.__class__))
            if "PUT" in resource['allowed_request_methods']:
                update_callable = _generate_update_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'], resource['put_syntaxes'])
                setattr(self, update_callable.__name__, new.instancemethod(update_callable, self, self.__class__))
            if "POST" in resource['allowed_request_methods']:
                create_callable = _generate_create_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'], resource['post_syntaxes'])
                setattr(self, create_callable.__name__, new.instancemethod(create_callable, self, self.__class__))
            if "DELETE" in resource['allowed_request_methods']:
                delete_callable = _generate_delete_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'])
                setattr(self, delete_callable.__name__, new.instancemethod(delete_callable, self, self.__class__))

    def print_help(self):
        """Prints the api method info to stdout for debugging."""
        keyfunc = lambda x: (x.resource_name, x.__doc__.strip())
        resources = groupby(sorted(filter(lambda x: (hasattr(x, 'is_api_call') and x.is_api_call and x.is_supported_api), [getattr(self, resource) for resource in dir(self)]), key=keyfunc), key=keyfunc)
        for resource_desc, resource_methods in resources:
            print resource_desc[0]
            print '\t', resource_desc[1]
            print
            print '\t', 'Available methods:'
            for r in resource_methods:
                method_header = r.__name__ + '('
                if r._get_args:
                    method_header += ','.join(r._get_args)
                if r._put_or_post_args:
                    put_or_post_args = [arg['name'] for arg in reduce(lambda x, y: x+y, r._put_or_post_args.values())]
                    method_header += ',{' +  ','.join(put_or_post_args) + '}'
                method_header += ')'
                method_desc = ""
                if r.__name__.startswith('create'):
                    method_desc = 'Corresponding API call: POST to ' + r._resource_uri
                if r.__name__.startswith('update'):
                    method_desc = 'Corresponding API call: PUT to ' + r._resource_uri
                if r.__name__.startswith('read'):
                    method_desc = 'Corresponding API call: GET to ' + r._resource_uri
                if r.__name__.startswith('delete'):
                    method_desc = 'Corresponding API call: DELETE to ' + r._resource_uri

                print '\t\t', method_header, ' - ', method_desc
            print

    def _constructRequest(self, version):
        """
        Utility for constructing the request header and connection
        """
        if not version: version = self.api_version
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "Accept" : "application/json",
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
        }
        if version: head[API_VERSION_HEADER_NAME] = version
        return conn, head

    def _deleteResource(self, url, version=None):
        """
        DELETEs the resource at url
        """
        conn, head = self._constructRequest(version)
        conn.request("DELETE", url, "", head)
        resp = conn.getresponse()
        self._handle_response_errors('DELETE', url, resp)

    def _getJSON(self, url, version=None):
        """
        GETs the resource at url, parses it as JSON, and returns the resulting data structure
        """
        conn, head = self._constructRequest(version)
        conn.request("GET", url, "", head)
        resp = conn.getresponse()
        self._handle_response_errors('GET', url, resp)
        return json.loads(resp.read())

    def _getData(self, url, version=None, accept=None):
        """
        GETs the resource at url and returns the raw response
        If the accept parameter is not None, the request passes is as the Accept header
        """
        if not version: version = self.api_version
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
        }
        if accept: head['Accept'] = accept
        if version: head[API_VERSION_HEADER_NAME] = version
        conn.request("GET", url, "", head)
        resp = conn.getresponse()
        self._handle_response_errors('GET', url, resp)
        return resp.read()

    def _putOrPostMultipart(self, method, url, data):
        """
        encodes the data as a multipart form and PUTs or POSTs to the url
        the response is parsed as JSON and the returns the resulting data structure
        """
        fields = []
        files = []
        for key, value in data.items():
            if type(value) == file:
                files.append((key, value.name, value.read()))
            else:
                fields.append((key, value))
        content_type, body = _encode_multipart_formdata(fields, files)
        if self.parsed_endpoint.scheme == 'https':
            h = httplib.HTTPS(self.parsed_endpoint.netloc)
        else:
            h = httplib.HTTP(self.parsed_endpoint.netloc)
        h.putrequest(method, url)
        h.putheader('Content-Type', content_type)
        h.putheader('Content-Length', str(len(body)))
        h.putheader('Accept', 'application/json')
        h.putheader('User-Agent', USER_AGENT)
        h.putheader(API_TOKEN_HEADER_NAME, self.api_token)
        h.putheader(API_VERSION_HEADER_NAME, self.api_version)
        h.endheaders()
        h.send(body)
        errcode, errmsg, headers = h.getreply()
        if errcode != 200: raise IOError('Response to %s to URL %s was status code %s: %s' % (method, url, errcode, h.file.read()))
        return json.loads(h.file.read())

    def _putOrPostJSON(self, method, url, data):
        """
        urlencodes the data and PUTs it to the url
        the response is parsed as JSON and the resulting data type is returned
        """
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "Content-Type" : "application/x-www-form-urlencoded",
            "Accept" : "application/json",
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
            API_VERSION_HEADER_NAME: self.api_version,
        }
        conn.request(method, url, urllib.urlencode(data), head)
        resp = conn.getresponse()
        self._handle_response_errors(method, url, resp)
        return json.loads(resp.read())

    def _generate_url(self, regex, arguments):
        """
        Uses the regex (of the type defined in Django's url patterns) and the arguments to return a relative URL
        For example, if the regex is '^/api/shreddr/job/(?P<id>[\d]+)$' and arguments is ['23'] then return would be '/api/shreddr/job/23'
        """
        regex_tokens = _split_regex(regex)
        result = ''
        for i in range(len(arguments)):
            result = result + str(regex_tokens[i]) + str(arguments[i])
        if len(regex_tokens) > len(arguments): result += regex_tokens[-1]
        return result
        #return '%s://%s/%s' % (self.parsed_endpoint.scheme, self.parsed_endpoint.netloc, result)

    def _handle_response_errors(self, method, url, response):
        if response.status == 200:
            return
        raise IOError('Response to %s to URL %s was status code %s: %s' % (
                       method, url, response.status, response.read()))

    def read_example_job(self):
        '''
        Convenience method for pulling out the example job.  Used in the
        Captricity API Quickstart.
        '''
        for job in self.read_jobs():
            if job['is_example']:
                return job

    def launch_job(self, job_id):
        '''
        Convenience method for launching a job.  We use POST for actions
        outside of HTTP verbs (job launch in this case).
        '''
        try:
            self.create_job(job_id, {'submit_job_action':True})
        except ValueError:
            pass
        return self.read_job(job_id)

def parse_date_string(date_string):
    """Converts the date strings created by the API (e.g. '2012-04-06T19:11:33.032') and returns an equivalent datetime instance."""
    return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f")

def _encode_multipart_formdata(fields, files):
    """
    Create a multipart encoded form for use in PUTing and POSTing.

    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = '----------A_vEry_UnlikelY_bouNdary_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append(str('Content-Disposition: form-data; name="%s"' % key))
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append(str('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

def get_content_type(filename):
    """Use the python mimetypes to determine a mime type, or return application/octet-stream"""
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

def _generate_read_callable(name, display_name, arguments, regex, doc, supported):
    """Returns a callable which conjures the URL for the resource and GETs a response"""
    def f(self, *args, **kwargs):
        url = self._generate_url(regex, args)
        if 'params' in kwargs: url += "?" + urllib.urlencode(kwargs['params'])
        if 'accept' in kwargs: return self._getData(url, accept=kwargs['accept'])
        return self._getJSON(url)
    f.__name__ = str('read_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = None
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_update_callable(name, display_name, arguments, regex, doc, supported, put_arguments):
    """Returns a callable which conjures the URL for the resource and PUTs data"""
    def f(self, *args, **kwargs):
        for key, value in args[-1].items():
            if type(value) == file:
                return self._putOrPostMultipart('PUT', self._generate_url(regex, args[:-1]), args[-1])
        return self._putOrPostJSON('PUT', self._generate_url(regex, args[:-1]), args[-1])
    f.__name__ = str('update_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = put_arguments
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_create_callable(name, display_name, arguments, regex, doc, supported, post_arguments):
    """Returns a callable which conjures the URL for the resource and POSTs data"""
    def f(self, *args, **kwargs):
        for key, value in args[-1].items():
            if type(value) == file:
                return self._putOrPostMultipart('POST', self._generate_url(regex, args[:-1]), args[-1])
        return self._putOrPostJSON('POST', self._generate_url(regex, args[:-1]), args[-1])
    f.__name__ = str('create_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = post_arguments
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_delete_callable(name, display_name, arguments, regex, doc, supported):
    def f(self, *args, **kwargs):
        return self._deleteResource(self._generate_url(regex, args))
    f.__name__ = str('delete_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = None
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _split_regex(regex):
    """
    Return an array of the URL split at each regex match like (?P<id>[\d]+)
    Call with a regex of '^/foo/(?P<id>[\d]+)/bar/$' and you will receive ['/foo/', '/bar/']
    """
    if regex[0] == '^': regex = regex[1:]
    if regex[-1] == '$': regex = regex[0:-1]
    results = []
    line = ''
    for c in regex:
        if c == '(':
            results.append(line)
            line = ''
        elif c == ')':
            line = ''
        else:
            line = line + c
    if len(line) > 0: results.append(line)
    return results
<commit_msg>Be smarter about when to parse responses as json<commit_after>"""
Utilites for accessing the Captricity APIs

NOTE: Methods which start with an underscore (_) are for internal use only and WILL change.
      Do not write your code against them.

"""
import new
import types
import random
import urllib
import httplib
import urlparse
import traceback
import mimetypes
import json
import Image
from StringIO import StringIO
from itertools import groupby
from hashlib import sha256
from urllib import urlencode
from datetime import datetime

API_TOKEN_HEADER_NAME = 'X_API_TOKEN'
API_VERSION_HEADER_NAME = 'X_API_VERSION'
CLIENT_VERSION = '0.01'
USER_AGENT = 'Captricity Python Client %s' % CLIENT_VERSION

class Client(object):
    """
    A network client for the Captricity third-party API.
    This class will fetch the API description from the endpoint URL and dynamically create methods for accessing the API.
    So, once you instantiate the Client you will be able to call cliend.read_job() even though you won't see that method defined below.

    To see a list of all of the api related methods that this client offers, do the following:
    client = Client('your api token', 'http://host/api/backbone/schreddr')
    client.print_help()
    """
    def __init__(self, api_token, endpoint="https://shreddr.captricity.com/api/backbone/schema"):
        """
        endpoint must be the full url to the API schema document, like `http://127.0.0.1:8000/api/backbone/schema'
        api_token is the unique string associated with your account's profile which allows API access
        """
        self.api_token = api_token
        self.endpoint = endpoint
        self.parsed_endpoint = urlparse.urlparse(self.endpoint)
        self.api_version = None
        self.schema = self._getData(self.parsed_endpoint.path, version=None)
        self.api_version = self.schema['version']

        for resource in self.schema['resources']:
            if "GET" in resource['allowed_request_methods']:
                read_callable = _generate_read_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'])
                setattr(self, read_callable.__name__, new.instancemethod(read_callable, self, self.__class__))
            if "PUT" in resource['allowed_request_methods']:
                update_callable = _generate_update_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'], resource['put_syntaxes'])
                setattr(self, update_callable.__name__, new.instancemethod(update_callable, self, self.__class__))
            if "POST" in resource['allowed_request_methods']:
                create_callable = _generate_create_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'], resource['post_syntaxes'])
                setattr(self, create_callable.__name__, new.instancemethod(create_callable, self, self.__class__))
            if "DELETE" in resource['allowed_request_methods']:
                delete_callable = _generate_delete_callable(resource['name'], resource['display_name'], resource['arguments'], resource['regex'], resource['doc'], resource['supported'])
                setattr(self, delete_callable.__name__, new.instancemethod(delete_callable, self, self.__class__))

    def print_help(self):
        """Prints the api method info to stdout for debugging."""
        keyfunc = lambda x: (x.resource_name, x.__doc__.strip())
        resources = groupby(sorted(filter(lambda x: (hasattr(x, 'is_api_call') and x.is_api_call and x.is_supported_api), [getattr(self, resource) for resource in dir(self)]), key=keyfunc), key=keyfunc)
        for resource_desc, resource_methods in resources:
            print resource_desc[0]
            print '\t', resource_desc[1]
            print
            print '\t', 'Available methods:'
            for r in resource_methods:
                method_header = r.__name__ + '('
                if r._get_args:
                    method_header += ','.join(r._get_args)
                if r._put_or_post_args:
                    put_or_post_args = [arg['name'] for arg in reduce(lambda x, y: x+y, r._put_or_post_args.values())]
                    method_header += ',{' +  ','.join(put_or_post_args) + '}'
                method_header += ')'
                method_desc = ""
                if r.__name__.startswith('create'):
                    method_desc = 'Corresponding API call: POST to ' + r._resource_uri
                if r.__name__.startswith('update'):
                    method_desc = 'Corresponding API call: PUT to ' + r._resource_uri
                if r.__name__.startswith('read'):
                    method_desc = 'Corresponding API call: GET to ' + r._resource_uri
                if r.__name__.startswith('delete'):
                    method_desc = 'Corresponding API call: DELETE to ' + r._resource_uri

                print '\t\t', method_header, ' - ', method_desc
            print

    def _constructRequest(self, version):
        """
        Utility for constructing the request header and connection
        """
        if not version: version = self.api_version
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "Accept" : "application/json",
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
        }
        if version: head[API_VERSION_HEADER_NAME] = version
        return conn, head

    def _deleteResource(self, url, version=None):
        """
        DELETEs the resource at url
        """
        conn, head = self._constructRequest(version)
        conn.request("DELETE", url, "", head)
        resp = conn.getresponse()
        self._handle_response_errors('DELETE', url, resp)

    def _getData(self, url, version=None, accept=None):
        """
        GETs the resource at url and returns the raw response
        If the accept parameter is not None, the request passes is as the Accept header
        """
        if not version: version = self.api_version
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
        }
        if accept: head['Accept'] = accept
        if version: head[API_VERSION_HEADER_NAME] = version
        conn.request("GET", url, "", head)
        resp = conn.getresponse()
        self._handle_response_errors('GET', url, resp)
        content_type = resp.getheader('content-type')
        if content_type == 'application/json':
            return json.loads(resp.read())
        return resp.read()

    def _putOrPostMultipart(self, method, url, data):
        """
        encodes the data as a multipart form and PUTs or POSTs to the url
        the response is parsed as JSON and the returns the resulting data structure
        """
        fields = []
        files = []
        for key, value in data.items():
            if type(value) == file:
                files.append((key, value.name, value.read()))
            else:
                fields.append((key, value))
        content_type, body = _encode_multipart_formdata(fields, files)
        if self.parsed_endpoint.scheme == 'https':
            h = httplib.HTTPS(self.parsed_endpoint.netloc)
        else:
            h = httplib.HTTP(self.parsed_endpoint.netloc)
        h.putrequest(method, url)
        h.putheader('Content-Type', content_type)
        h.putheader('Content-Length', str(len(body)))
        h.putheader('Accept', 'application/json')
        h.putheader('User-Agent', USER_AGENT)
        h.putheader(API_TOKEN_HEADER_NAME, self.api_token)
        h.putheader(API_VERSION_HEADER_NAME, self.api_version)
        h.endheaders()
        h.send(body)
        errcode, errmsg, headers = h.getreply()
        if errcode != 200: raise IOError('Response to %s to URL %s was status code %s: %s' % (method, url, errcode, h.file.read()))
        return json.loads(h.file.read())

    def _putOrPostJSON(self, method, url, data):
        """
        urlencodes the data and PUTs it to the url
        the response is parsed as JSON and the resulting data type is returned
        """
        if self.parsed_endpoint.scheme == 'https':
            conn = httplib.HTTPSConnection(self.parsed_endpoint.netloc)
        else:
            conn = httplib.HTTPConnection(self.parsed_endpoint.netloc)
        head = {
            "Content-Type" : "application/x-www-form-urlencoded",
            "Accept" : "application/json",
            "User-Agent": USER_AGENT,
            API_TOKEN_HEADER_NAME: self.api_token,
            API_VERSION_HEADER_NAME: self.api_version,
        }
        conn.request(method, url, urllib.urlencode(data), head)
        resp = conn.getresponse()
        self._handle_response_errors(method, url, resp)
        return json.loads(resp.read())

    def _generate_url(self, regex, arguments):
        """
        Uses the regex (of the type defined in Django's url patterns) and the arguments to return a relative URL
        For example, if the regex is '^/api/shreddr/job/(?P<id>[\d]+)$' and arguments is ['23'] then return would be '/api/shreddr/job/23'
        """
        regex_tokens = _split_regex(regex)
        result = ''
        for i in range(len(arguments)):
            result = result + str(regex_tokens[i]) + str(arguments[i])
        if len(regex_tokens) > len(arguments): result += regex_tokens[-1]
        return result
        #return '%s://%s/%s' % (self.parsed_endpoint.scheme, self.parsed_endpoint.netloc, result)

    def _handle_response_errors(self, method, url, response):
        if response.status == 200:
            return
        raise IOError('Response to %s to URL %s was status code %s: %s' % (
                       method, url, response.status, response.read()))

    def read_example_job(self):
        '''
        Convenience method for pulling out the example job.  Used in the
        Captricity API Quickstart.
        '''
        for job in self.read_jobs():
            if job['is_example']:
                return job

    def launch_job(self, job_id):
        '''
        Convenience method for launching a job.  We use POST for actions
        outside of HTTP verbs (job launch in this case).
        '''
        try:
            self.create_job(job_id, {'submit_job_action':True})
        except ValueError:
            pass
        return self.read_job(job_id)

def parse_date_string(date_string):
    """Converts the date strings created by the API (e.g. '2012-04-06T19:11:33.032') and returns an equivalent datetime instance."""
    return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f")

def _encode_multipart_formdata(fields, files):
    """
    Create a multipart encoded form for use in PUTing and POSTing.

    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = '----------A_vEry_UnlikelY_bouNdary_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append(str('Content-Disposition: form-data; name="%s"' % key))
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append(str('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

def get_content_type(filename):
    """Use the python mimetypes to determine a mime type, or return application/octet-stream"""
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

def _generate_read_callable(name, display_name, arguments, regex, doc, supported):
    """Returns a callable which conjures the URL for the resource and GETs a response"""
    def f(self, *args, **kwargs):
        url = self._generate_url(regex, args)
        if 'params' in kwargs: url += "?" + urllib.urlencode(kwargs['params'])
        return self._getData(url, accept=(kwargs['accept'] if 'accept' in kwargs else None))
    f.__name__ = str('read_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = None
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_update_callable(name, display_name, arguments, regex, doc, supported, put_arguments):
    """Returns a callable which conjures the URL for the resource and PUTs data"""
    def f(self, *args, **kwargs):
        for key, value in args[-1].items():
            if type(value) == file:
                return self._putOrPostMultipart('PUT', self._generate_url(regex, args[:-1]), args[-1])
        return self._putOrPostJSON('PUT', self._generate_url(regex, args[:-1]), args[-1])
    f.__name__ = str('update_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = put_arguments
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_create_callable(name, display_name, arguments, regex, doc, supported, post_arguments):
    """Returns a callable which conjures the URL for the resource and POSTs data"""
    def f(self, *args, **kwargs):
        for key, value in args[-1].items():
            if type(value) == file:
                return self._putOrPostMultipart('POST', self._generate_url(regex, args[:-1]), args[-1])
        return self._putOrPostJSON('POST', self._generate_url(regex, args[:-1]), args[-1])
    f.__name__ = str('create_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = post_arguments
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _generate_delete_callable(name, display_name, arguments, regex, doc, supported):
    def f(self, *args, **kwargs):
        return self._deleteResource(self._generate_url(regex, args))
    f.__name__ = str('delete_%s' % name)
    f.__doc__ = doc
    f._resource_uri = regex
    f._get_args = arguments
    f._put_or_post_args = None
    f.resource_name = display_name
    f.is_api_call = True
    f.is_supported_api = supported
    return f

def _split_regex(regex):
    """
    Return an array of the URL split at each regex match like (?P<id>[\d]+)
    Call with a regex of '^/foo/(?P<id>[\d]+)/bar/$' and you will receive ['/foo/', '/bar/']
    """
    if regex[0] == '^': regex = regex[1:]
    if regex[-1] == '$': regex = regex[0:-1]
    results = []
    line = ''
    for c in regex:
        if c == '(':
            results.append(line)
            line = ''
        elif c == ')':
            line = ''
        else:
            line = line + c
    if len(line) > 0: results.append(line)
    return results
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.md', which is part of this source code package.
#

import re
from os.path import expanduser, isfile
import yaml
from yaml import YAMLError

DEFAULT_KUBECONFIG = "{0}/.kube/config".format(expanduser("~"))
DEFAULT_API_HOST = "localhost:8888"
DEFAULT_API_VERSION = "v1"
DEFAULT_NAMESPACE = "default"

VALID_API_VERSIONS = ["v1"]

VALID_IP_RE = re.compile(r'^(http[s]?\:\/\/)?((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(:[0-9]+)?$')
VALID_HOST_RE = re.compile(r'^(http[s]?\:\/\/)?([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-\.]*[A-Za-z])+(:[0-9]+)?$')

class K8sConfig(object):

    def __init__(self, kubeconfig=DEFAULT_KUBECONFIG, api_host=DEFAULT_API_HOST, auth=None, cert=None,
                 namespace=DEFAULT_NAMESPACE, pull_secret=None, token=None, version=DEFAULT_API_VERSION):
        """
        Pulls configuration from a kubeconfig file, if present, otherwise accepts user-defined parameters.
        See http://kubernetes.io/docs/user-guide/kubeconfig-file/ for information on the kubeconfig file.

        :param kubeconfig: Absolute path to the kubeconfig file, if any.
        :param api_host: Absolute URI where the API server resides.
        :param auth: A tuple of (username, password) for basic authentication.
        :param namespace: The namespace to use. Defaults to 'default'
        :param pull_secret: The password to use when pulling images from the container repository.
        :param token: An authentication token. Mutually exclusive with 'auth'.
        :param version: The version of the API to target. Defaults to 'v1'.
        """

        super(K8sConfig, self).__init__()

        self.api_host = None
        self.auth = None
        self.ca_cert = None
        self.ca_cert_data = None
        self.cert = None
        self.client_certificate = None
        self.client_key = None
        self.pull_secret = None
        self.token = None

        dotconf = None
        if kubeconfig is not None:
            if not isfile(kubeconfig):
                raise IOError('K8sConfig: kubeconfig: [ {0} ] doesn\'t exist.'.format(kubeconfig))
            try:
                with open(kubeconfig, 'r') as stream:
                    dotconf = yaml.load(stream)
            except YAMLError as err:
                raise SyntaxError('K8sConfig: kubeconfig: [ {0} ] is not a valid YAML file.'.format(kubeconfig))

        # we're pulling configuration from a kubeconfig file
        if dotconf is not None:
            self.clusters = dotconf['clusters']
            self.contexts = dotconf['contexts']
            self.current_context = dotconf['current-context']
            self.current_context_dict = [context['context']
                                         for context in self.contexts
                                         if context['name'] == self.current_context][0]
            self.preferences = dotconf['preferences']
            self.pull_secret = pull_secret
            self.users = dotconf['users']
            self.version = dotconf['apiVersion']

            if self.clusters:
                for cluster in self.clusters:
                    if cluster['name'] == self.current_context_dict['cluster']:
                        if 'server' in cluster['cluster']:
                            self.api_host = cluster['cluster']['server']
                        if 'certificate-authority' in cluster['cluster']:
                            self.ca_cert = cluster['cluster']['certificate-authority']
                        if 'certificate-authority-data' in cluster['cluster']:
                            self.ca_cert_data = cluster['cluster']['certificate-authority-data']

            if self.users:
                for user in self.users:
                    if user['name'] == self.current_context_dict['user']:
                        if 'username' in user['user'] and 'password' in user['user']:
                            self.auth = (user['user']['username'], user['user']['password'])
                        if 'token' in user['user']:
                            self.token = user['user']['token']
                        if 'client-certificate' in user['user'] and 'client-key' in user['user']:
                            self.client_certificate = user['user']['client-certificate']
                            self.client_key = user['user']['client-key']
                            self.cert = (self.client_certificate, self.client_key)

            if self.contexts:
                for context in self.contexts:
                    if context['name'] == self.current_context:
                        if 'namespace' in context['context']:
                            self.namespace = context['context']['namespace']
                        else:
                            self.namespace = namespace

        # we're using user-supplied config; run some sanity checks.
        if dotconf is None:

            if not isinstance(api_host, str) or not isinstance(version, str):
                raise SyntaxError('K8sConfig: host: [ {0} ] and version: [ {1} ] must be strings.'.format(api_host, version))
            if not (VALID_IP_RE.match(api_host) or VALID_HOST_RE.match(api_host)):
                raise SyntaxError('K8sConfig: host: [ {0} ] is invalid.'.format(api_host))
            if auth is not None and not isinstance(auth, tuple):
                raise SyntaxError('K8sConfig: auth: [ {0} ] must be a tuple for basic authentication.'.format(auth))
            if not isinstance(namespace, str):
                raise SyntaxError('K8sConfig: namespace: [ {0} ] must be a string.'.format(namespace))
            if pull_secret is not None and not isinstance(pull_secret, list):
                raise SyntaxError('K8sConfig: pull_secret: [ {0} ] must be a list.'.format(pull_secret))
            if token is not None and not isinstance(token, str):
                raise SyntaxError('K8sConfig: token: [ {0} ] must be a string.'.format(token))
            if version not in VALID_API_VERSIONS:
                valid = ", ".join(VALID_API_VERSIONS)
                raise SyntaxError('K8sConfig: api_version: [ {0} ] must be in: [ {1} ]'.format(version, valid))

            schema_re = re.compile(r"^http[s]*")
            if not schema_re.search(api_host):
                api_host = "http://{0}".format(api_host)
            self.api_host = api_host
            self.auth = auth
            self.cert = cert
            self.namespace = namespace
            self.pull_secret = pull_secret
            self.token = token
            self.version = version

    def serialize(self):
        data = {}
        if self.api_host is not None:
            data['api_host'] = self.api_host
        if self.auth is not None:
            data['auth'] = self.auth
        if self.cert is not None:
            data['cert'] = self.cert
        if self.namespace is not None:
            data['namespace'] = self.namespace
        if self.pull_secret is not None:
            data['pull_secret'] = self.pull_secret
        if self.token is not None:
            data['token'] = self.token
        if self.version is not None:
            data['version'] = self.version
        return data
<commit_msg>K8sConfig: PEP8<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.md', which is part of this source code package.
#

import re
from os.path import expanduser, isfile

import yaml
from yaml import YAMLError

DEFAULT_KUBECONFIG = "{0}/.kube/config".format(expanduser("~"))
DEFAULT_API_HOST = "localhost:8888"
DEFAULT_API_VERSION = "v1"
DEFAULT_NAMESPACE = "default"

VALID_API_VERSIONS = ["v1"]

VALID_IP_RE = re.compile(
    r'^(http[s]?\:\/\/)?((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(:[0-9]+)?$')
VALID_HOST_RE = re.compile(r'^(http[s]?\:\/\/)?([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-\.]*[A-Za-z])+(:[0-9]+)?$')

class K8sConfig(object):
    def __init__(self, kubeconfig=DEFAULT_KUBECONFIG, api_host=DEFAULT_API_HOST, auth=None, cert=None,
                 namespace=DEFAULT_NAMESPACE, pull_secret=None, token=None, version=DEFAULT_API_VERSION):
        """
        Pulls configuration from a kubeconfig file, if present, otherwise accepts user-defined parameters.
        See http://kubernetes.io/docs/user-guide/kubeconfig-file/ for information on the kubeconfig file.

        :param kubeconfig: Absolute path to the kubeconfig file, if any.
        :param api_host: Absolute URI where the API server resides.
        :param auth: A tuple of (username, password) for basic authentication.
        :param namespace: The namespace to use. Defaults to 'default'
        :param pull_secret: The password to use when pulling images from the container repository.
        :param token: An authentication token. Mutually exclusive with 'auth'.
        :param version: The version of the API to target. Defaults to 'v1'.
        """

        super(K8sConfig, self).__init__()

        self.api_host = None
        self.auth = None
        self.ca_cert = None
        self.ca_cert_data = None
        self.cert = None
        self.client_certificate = None
        self.client_key = None
        self.pull_secret = None
        self.token = None

        dotconf = None
        if kubeconfig is not None:
            if not isfile(kubeconfig):
                raise IOError('K8sConfig: kubeconfig: [ {0} ] doesn\'t exist.'.format(kubeconfig))
            try:
                with open(kubeconfig, 'r') as stream:
                    dotconf = yaml.load(stream)
            except YAMLError as err:
                raise SyntaxError('K8sConfig: kubeconfig: [ {0} ] is not a valid YAML file.'.format(kubeconfig))

        # we're pulling configuration from a kubeconfig file
        if dotconf is not None:
            self.clusters = dotconf['clusters']
            self.contexts = dotconf['contexts']
            self.current_context = dotconf['current-context']
            self.current_context_dict = [context['context']
                                         for context in self.contexts
                                         if context['name'] == self.current_context][0]
            self.preferences = dotconf['preferences']
            self.pull_secret = pull_secret
            self.users = dotconf['users']
            self.version = dotconf['apiVersion']

            if self.clusters:
                for cluster in self.clusters:
                    if cluster['name'] == self.current_context_dict['cluster']:
                        if 'server' in cluster['cluster']:
                            self.api_host = cluster['cluster']['server']
                        if 'certificate-authority' in cluster['cluster']:
                            self.ca_cert = cluster['cluster']['certificate-authority']
                        if 'certificate-authority-data' in cluster['cluster']:
                            self.ca_cert_data = cluster['cluster']['certificate-authority-data']

            if self.users:
                for user in self.users:
                    if user['name'] == self.current_context_dict['user']:
                        if 'username' in user['user'] and 'password' in user['user']:
                            self.auth = (user['user']['username'], user['user']['password'])
                        if 'token' in user['user']:
                            self.token = user['user']['token']
                        if 'client-certificate' in user['user'] and 'client-key' in user['user']:
                            self.client_certificate = user['user']['client-certificate']
                            self.client_key = user['user']['client-key']
                            self.cert = (self.client_certificate, self.client_key)

            if self.contexts:
                for context in self.contexts:
                    if context['name'] == self.current_context:
                        if 'namespace' in context['context']:
                            self.namespace = context['context']['namespace']
                        else:
                            self.namespace = namespace

        # we're using user-supplied config; run some sanity checks.
        if dotconf is None:

            if not isinstance(api_host, str) or not isinstance(version, str):
                raise SyntaxError(
                    'K8sConfig: host: [ {0} ] and version: [ {1} ] must be strings.'.format(api_host, version))
            if not (VALID_IP_RE.match(api_host) or VALID_HOST_RE.match(api_host)):
                raise SyntaxError('K8sConfig: host: [ {0} ] is invalid.'.format(api_host))
            if auth is not None and not isinstance(auth, tuple):
                raise SyntaxError('K8sConfig: auth: [ {0} ] must be a tuple for basic authentication.'.format(auth))
            if not isinstance(namespace, str):
                raise SyntaxError('K8sConfig: namespace: [ {0} ] must be a string.'.format(namespace))
            if pull_secret is not None and not isinstance(pull_secret, list):
                raise SyntaxError('K8sConfig: pull_secret: [ {0} ] must be a list.'.format(pull_secret))
            if token is not None and not isinstance(token, str):
                raise SyntaxError('K8sConfig: token: [ {0} ] must be a string.'.format(token))
            if version not in VALID_API_VERSIONS:
                valid = ", ".join(VALID_API_VERSIONS)
                raise SyntaxError('K8sConfig: api_version: [ {0} ] must be in: [ {1} ]'.format(version, valid))

            schema_re = re.compile(r"^http[s]*")
            if not schema_re.search(api_host):
                api_host = "http://{0}".format(api_host)
            self.api_host = api_host
            self.auth = auth
            self.cert = cert
            self.namespace = namespace
            self.pull_secret = pull_secret
            self.token = token
            self.version = version

    def serialize(self):
        data = {}
        if self.api_host is not None:
            data['api_host'] = self.api_host
        if self.auth is not None:
            data['auth'] = self.auth
        if self.cert is not None:
            data['cert'] = self.cert
        if self.namespace is not None:
            data['namespace'] = self.namespace
        if self.pull_secret is not None:
            data['pull_secret'] = self.pull_secret
        if self.token is not None:
            data['token'] = self.token
        if self.version is not None:
            data['version'] = self.version
        return data
<|endoftext|>


<commit_before># coding= utf-8
"""Modelos definidos para o Projeto carcerópolis."""
import logging

from cidades.models import Cidade, STATE_CHOICES
from csv import DictReader
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from mezzanine.blog.models import BlogPost
from autoslug import AutoSlugField

from .options import current_month, current_year, MONTH_CHOICES, YEAR_CHOICES
from .validators import check_filetype

log = logging.getLogger(__name__)

class AreaDeAtuacao(models.Model):
    """Categorias Gerais de classificação de Especialistas e Publicações."""
    nome = models.CharField(max_length=250, unique=True,
                            verbose_name='Nome da área')
    descricao = models.TextField(verbose_name='Descrição')
    ordem = models.IntegerField(unique=True, verbose_name='Ordem')
    slug = AutoSlugField(populate_from='nome', always_update=True)

    def __unicode__(self):
        return self.nome

    class Meta:
        verbose_name = 'Área de Atuação'
        verbose_name_plural = 'Áreas de Atuação'

class Especialidade(models.Model):
    """Definição das Especialidades principais mapeadas no projeto."""
    nome = models.CharField(max_length=80, unique=True,
                            verbose_name='Nome da especialidade')
    descricao = models.TextField(blank=True, verbose_name='Descrição')
    slug = AutoSlugField(populate_from='nome',
                         always_update=True)

    def __unicode__(self):
        return self.nome

    class Meta:
        verbose_name = 'Especialidade'
        verbose_name_plural = 'Especialidades'

class Especialista(models.Model):
    """Classe que define os especialistas para o 'Banco de Especialistas'."""
    nome = models.CharField(max_length=250)
    email = models.EmailField()
    ddi = models.IntegerField(verbose_name='DDI', blank=True, null=True,
                              default=55)
    ddd = models.IntegerField(verbose_name='DDD', blank=True, null=True)
    telefone = models.IntegerField(verbose_name='Telefone', blank=True,
                                   null=True)
    mini_bio = models.CharField(max_length=600, blank=True)
    instituicao = models.CharField(max_length=250,
                                   verbose_name='Instituição')
    area_de_atuacao = models.ManyToManyField(AreaDeAtuacao,
                                             verbose_name='Área de atuação')
    especialidades = models.ManyToManyField(Especialidade)

    def __unicode__(self):
        return self.nome

    class Meta:
        verbose_name = 'Especialista'
        verbose_name_plural = 'Especialistas'

class Publicacao(BlogPost):
    """Publicações relacionadas à temática do site."""
    autoria = models.CharField(max_length=150, verbose_name='Autoria')
    categorias = models.ManyToManyField(AreaDeAtuacao,
                                        verbose_name='Categorias')
    ano_de_publicacao = models.IntegerField(verbose_name='Ano de publicação',
                                            choices=YEAR_CHOICES,
                                            default=current_year)
    arquivo_publicacao = models.FileField(upload_to='publicacoes/',
                                          verbose_name='Arquivo da publicação')

    class Meta:
        verbose_name = 'Publicação'
        verbose_name_plural = 'Publicações'

    def __unicode__(self):
        return self.title

Publicacao._meta.get_field('title').verbose_name = 'Título'
Publicacao._meta.get_field('publish_date').verbose_name = 'Publicado em'
Publicacao._meta.get_field('content').verbose_name = 'Descrição'
Publicacao._meta.get_field('keywords').verbose_name = 'Tags'
Publicacao._meta.get_field('related_posts').verbose_name = 'Posts Relacionados'
Publicacao._meta.get_field('_meta_title').verbose_name = 'Tílulo'
Publicacao._meta.get_field('description').verbose_name = 'Descrição curta'
Publicacao._meta.get_field('gen_description').verbose_name = 'Gerar descrição'
Publicacao._meta.get_field('allow_comments').default = False

class UnidadePrisional(models.Model):
    """Unidades Prisionais."""
    nome_unidade = models.CharField(max_length=255,
                                    verbose_name='Nome da Unidade')
    sigla_unidade = models.CharField(max_length=10)
    tipo_logradouro = models.CharField(max_length=20)
    nome_logradouro = models.CharField(max_length=255)
    numero = models.IntegerField(blank=True, null=True, verbose_name='Número')
    complemento = models.CharField(max_length=255, blank=True)
    bairro = models.CharField(max_length=255)
    municipio = models.ForeignKey(Cidade, verbose_name='Município')
    uf = models.CharField(max_length=2, choices=STATE_CHOICES)
    cep = models.CharField(max_length=8)
    ddd = models.IntegerField(verbose_name='DDD', null=True, blank=True)
    telefone = models.IntegerField(null=True, blank=True)
    email = models.EmailField()

    class Meta:
        verbose_name = 'Unidade Prisional'
        verbose_name_plural = 'Unidades Prisionais'

    def __unicode__(self):
        return "%s (%s/%s)" % (self.nome_unidade, self.municipio, self.uf)

    @classmethod
    def _new_from_dict(cls, data):
        """Generate a new 'Unidade Prisional' and return it.

        The 'data' attribute is a dictionary with the necessary fields to
        generate a new Unidade Prisional.
        """

        unidade = UnidadePrisional()
        unidade.nome_unidade = data['nome_unidade']
        unidade.sigla_unidade = data['sigla_unidade']
        unidade.tipo_logradouro = data['tipo_logradouro']
        unidade.nome_logradouro = data['nome_logradouro']
        if isinstance(data['numero'], int):
            unidade.numero = data['numero']
        else:
            try:
                unidade.numero = int(data['numero'])
            except:
                unidade.numero = None
        unidade.complemento = data['complemento']
        unidade.bairro = data['bairro']
        unidade.municipio = Cidade.objects.get(nome=data['municipio'],
                                               estado=data['uf'])
        unidade.uf = data['uf']
        unidade.cep = data['cep']
        if isinstance(data['ddd'], int):
            unidade.ddd = data['ddd']
        else:
            try:
                unidade.ddd = int(data['ddd'])
            except:
                unidade.ddd = None
        if isinstance(data['telefone'], int):
            unidade.telefone = data['telefone']
        else:
            try:
                unidade.telefone = int(data['telefone'])
            except:
                unidade.telefone = None
        unidade.email = data['email']
        return unidade

    @classmethod
    def _import_from_csv(cls, path):
        """Populate the DB from CSV data.

        The 'path' attribute is the absolute path to the CSV file that must be
        inserted on the database.
        """
        atualizadas = []
        novas = []
        errors = []

        fieldnames = ['remove1', 'nome_unidade', 'sigla_unidade',
                      'tipo_logradouro', 'nome_logradouro', 'numero',
                      'complemento', 'bairro', 'municipio', 'uf', 'cep', 'ddd',
                      'telefone', 'email', 'remove2']
        with open(path, 'r') as csv_file:
            data = DictReader(csv_file, fieldnames=fieldnames)

            for row in data:
                if row['nome_unidade'] == 'nome_unidade':
                    row = data.next()

                del row['remove1']
                del row['remove2']

                try:
                    unidade = UnidadePrisional.objects.get(
                        nome_unidade=row['nome_unidade'],
                        municipio=Cidade.objects.get(nome=row['municipio'],
                                                     estado=row['uf']))
                    unidade._update_from_dict(row)
                    unidade.save()
                    atualizadas.append(unidade.nome_unidade)
                except ObjectDoesNotExist:
                    try:
                        unidade = UnidadePrisional._new_from_dict(row)
                        unidade.save()
                        novas.append(unidade.nome_unidade)
                    except Exception as e:
                        error = {'nome_unidade': row['nome_unidade'],
                                 'erro': str(e),
                                 'data': row}
                        errors.append(error)

        msg = 'Resumo da operação:\n'
        if atualizadas:
            msg += '    - '
            msg += '{} unidades foram atualizadas.\n'.format(len(atualizadas))
            log.info('    {}'.format(atualizadas))

        if novas:
            msg += '    - '
            msg += '{} unidades foram adicionadas.\n'.format(len(novas))

        if errors:
            msg += 'Ocorreram {} erros de importação:\n'.format(len(errors))
            for error in errors:
                msg += '    - '
                msg += 'Unidade: {:.30}'.format(error['nome_unidade'])
                msg += ' | {} | {}/{}\n'.format(error['erro'],
                                                error['data']['uf'],
                                                error['data']['municipio'])

        log.info(msg)

    def _update_from_dict(self, data):
        """Update a 'Unidade Prisional' based on its name and return it.

        The 'data' attribute is a dictionary with the necessary fields to
        generate a new Unidade Prisional.
        """

        self.sigla_unidade = data['sigla_unidade']
        self.tipo_logradouro = data['tipo_logradouro']
        self.nome_logradouro = data['nome_logradouro']
        if isinstance(data['numero'], int):
            self.numero = data['numero']
        else:
            try:
                self.numero = int(data['numero'])
            except:
                self.numero = None
        self.complemento = data['complemento']
        self.bairro = data['bairro']
        self.municipio = Cidade.objects.get(nome=data['municipio'],
                                            estado=data['uf'])
        self.uf = data['uf']
        self.cep = data['cep']
        if isinstance(data['ddd'], int):
            self.ddd = data['ddd']
        else:
            try:
                self.ddd = int(data['ddd'])
            except:
                self.ddd = None
        if isinstance(data['telefone'], int):
            self.telefone = data['telefone']
        else:
            try:
                self.telefone = int(data['telefone'])
            except:
                self.telefone = None
        self.email = data['email']

class BaseMJ(models.Model):
    """Manage the multiple versions of MJ Infopen raw database."""

    ano = models.IntegerField(choices=YEAR_CHOICES, default=current_year)
    mes = models.CharField(verbose_name='Mês', max_length=40,
                           choices=MONTH_CHOICES, default=current_month)
    arquivo = models.FileField(upload_to='base_bruta_mj/',
                               validators=[check_filetype])
    salvo_em = models.DateTimeField(verbose_name='Salvo em', auto_now_add=True)

    def __unicode__(self):
        return "{}/{}".format(self.mes, self.ano)

    class Meta:
        verbose_name = 'Base bruta MJ'
        verbose_name_plural = 'Bases brutas MJ'

class ArquivoBaseCarceropolis(models.Model):
    """Manage the multiple raw versions of the cleaned MJ/Infopen database."""

    ano = models.IntegerField(choices=YEAR_CHOICES, default=current_year)
    mes = models.CharField(verbose_name='Mês', max_length=40,
                           choices=MONTH_CHOICES, default=current_month)
    arquivo = models.FileField(upload_to='base_bruta_carceropolis/',
                               validators=[check_filetype])
    salvo_em = models.DateTimeField(verbose_name='Salvo em', auto_now_add=True)

    def __unicode__(self):
        return "{}/{}".format(self.mes, self.ano)

    class Meta:
        verbose_name = u'Base bruta Carcerópolis'
        verbose_name_plural = u'Bases brutas Carcerópolis'
<commit_msg>Change __unicode__ for __str__, python2 x python3<commit_after># coding= utf-8
"""Modelos definidos para o Projeto carcerópolis."""
import logging

from cidades.models import Cidade, STATE_CHOICES
from csv import DictReader
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from mezzanine.blog.models import BlogPost
from autoslug import AutoSlugField

from .options import current_month, current_year, MONTH_CHOICES, YEAR_CHOICES
from .validators import check_filetype

log = logging.getLogger(__name__)

class AreaDeAtuacao(models.Model):
    """Categorias Gerais de classificação de Especialistas e Publicações."""
    nome = models.CharField(max_length=250, unique=True,
                            verbose_name='Nome da área')
    descricao = models.TextField(verbose_name='Descrição')
    ordem = models.IntegerField(unique=True, verbose_name='Ordem')
    slug = AutoSlugField(populate_from='nome', always_update=True)

    def __str__(self):
        return self.nome

    class Meta:
        verbose_name = 'Área de Atuação'
        verbose_name_plural = 'Áreas de Atuação'

class Especialidade(models.Model):
    """Definição das Especialidades principais mapeadas no projeto."""
    nome = models.CharField(max_length=80, unique=True,
                            verbose_name='Nome da especialidade')
    descricao = models.TextField(blank=True, verbose_name='Descrição')
    slug = AutoSlugField(populate_from='nome',
                         always_update=True)

    def __str__(self):
        return self.nome

    class Meta:
        verbose_name = 'Especialidade'
        verbose_name_plural = 'Especialidades'

class Especialista(models.Model):
    """Classe que define os especialistas para o 'Banco de Especialistas'."""
    nome = models.CharField(max_length=250)
    email = models.EmailField()
    ddi = models.IntegerField(verbose_name='DDI', blank=True, null=True,
                              default=55)
    ddd = models.IntegerField(verbose_name='DDD', blank=True, null=True)
    telefone = models.IntegerField(verbose_name='Telefone', blank=True,
                                   null=True)
    mini_bio = models.CharField(max_length=600, blank=True)
    instituicao = models.CharField(max_length=250,
                                   verbose_name='Instituição')
    area_de_atuacao = models.ManyToManyField(AreaDeAtuacao,
                                             verbose_name='Área de atuação')
    especialidades = models.ManyToManyField(Especialidade)

    def __str__(self):
        return self.nome

    class Meta:
        verbose_name = 'Especialista'
        verbose_name_plural = 'Especialistas'

class Publicacao(BlogPost):
    """Publicações relacionadas à temática do site."""
    autoria = models.CharField(max_length=150, verbose_name='Autoria')
    categorias = models.ManyToManyField(AreaDeAtuacao,
                                        verbose_name='Categorias')
    ano_de_publicacao = models.IntegerField(verbose_name='Ano de publicação',
                                            choices=YEAR_CHOICES,
                                            default=current_year)
    arquivo_publicacao = models.FileField(upload_to='publicacoes/',
                                          verbose_name='Arquivo da publicação')

    class Meta:
        verbose_name = 'Publicação'
        verbose_name_plural = 'Publicações'

    def __str__(self):
        return self.title

Publicacao._meta.get_field('title').verbose_name = 'Título'
Publicacao._meta.get_field('publish_date').verbose_name = 'Publicado em'
Publicacao._meta.get_field('content').verbose_name = 'Descrição'
Publicacao._meta.get_field('keywords').verbose_name = 'Tags'
Publicacao._meta.get_field('related_posts').verbose_name = 'Posts Relacionados'
Publicacao._meta.get_field('_meta_title').verbose_name = 'Tílulo'
Publicacao._meta.get_field('description').verbose_name = 'Descrição curta'
Publicacao._meta.get_field('gen_description').verbose_name = 'Gerar descrição'
Publicacao._meta.get_field('allow_comments').default = False

class UnidadePrisional(models.Model):
    """Unidades Prisionais."""
    nome_unidade = models.CharField(max_length=255,
                                    verbose_name='Nome da Unidade')
    sigla_unidade = models.CharField(max_length=10)
    tipo_logradouro = models.CharField(max_length=20)
    nome_logradouro = models.CharField(max_length=255)
    numero = models.IntegerField(blank=True, null=True, verbose_name='Número')
    complemento = models.CharField(max_length=255, blank=True)
    bairro = models.CharField(max_length=255)
    municipio = models.ForeignKey(Cidade, verbose_name='Município')
    uf = models.CharField(max_length=2, choices=STATE_CHOICES)
    cep = models.CharField(max_length=8)
    ddd = models.IntegerField(verbose_name='DDD', null=True, blank=True)
    telefone = models.IntegerField(null=True, blank=True)
    email = models.EmailField()

    class Meta:
        verbose_name = 'Unidade Prisional'
        verbose_name_plural = 'Unidades Prisionais'

    def __str__(self):
        return "%s (%s/%s)" % (self.nome_unidade, self.municipio, self.uf)

    @classmethod
    def _new_from_dict(cls, data):
        """Generate a new 'Unidade Prisional' and return it.

        The 'data' attribute is a dictionary with the necessary fields to
        generate a new Unidade Prisional.
        """

        unidade = UnidadePrisional()
        unidade.nome_unidade = data['nome_unidade']
        unidade.sigla_unidade = data['sigla_unidade']
        unidade.tipo_logradouro = data['tipo_logradouro']
        unidade.nome_logradouro = data['nome_logradouro']
        if isinstance(data['numero'], int):
            unidade.numero = data['numero']
        else:
            try:
                unidade.numero = int(data['numero'])
            except:
                unidade.numero = None
        unidade.complemento = data['complemento']
        unidade.bairro = data['bairro']
        unidade.municipio = Cidade.objects.get(nome=data['municipio'],
                                               estado=data['uf'])
        unidade.uf = data['uf']
        unidade.cep = data['cep']
        if isinstance(data['ddd'], int):
            unidade.ddd = data['ddd']
        else:
            try:
                unidade.ddd = int(data['ddd'])
            except:
                unidade.ddd = None
        if isinstance(data['telefone'], int):
            unidade.telefone = data['telefone']
        else:
            try:
                unidade.telefone = int(data['telefone'])
            except:
                unidade.telefone = None
        unidade.email = data['email']
        return unidade

    @classmethod
    def _import_from_csv(cls, path):
        """Populate the DB from CSV data.

        The 'path' attribute is the absolute path to the CSV file that must be
        inserted on the database.
        """
        atualizadas = []
        novas = []
        errors = []

        fieldnames = ['remove1', 'nome_unidade', 'sigla_unidade',
                      'tipo_logradouro', 'nome_logradouro', 'numero',
                      'complemento', 'bairro', 'municipio', 'uf', 'cep', 'ddd',
                      'telefone', 'email', 'remove2']
        with open(path, 'r') as csv_file:
            data = DictReader(csv_file, fieldnames=fieldnames)

            for row in data:
                if row['nome_unidade'] == 'nome_unidade':
                    row = data.next()

                del row['remove1']
                del row['remove2']

                try:
                    unidade = UnidadePrisional.objects.get(
                        nome_unidade=row['nome_unidade'],
                        municipio=Cidade.objects.get(nome=row['municipio'],
                                                     estado=row['uf']))
                    unidade._update_from_dict(row)
                    unidade.save()
                    atualizadas.append(unidade.nome_unidade)
                except ObjectDoesNotExist:
                    try:
                        unidade = UnidadePrisional._new_from_dict(row)
                        unidade.save()
                        novas.append(unidade.nome_unidade)
                    except Exception as e:
                        error = {'nome_unidade': row['nome_unidade'],
                                 'erro': str(e),
                                 'data': row}
                        errors.append(error)

        msg = 'Resumo da operação:\n'
        if atualizadas:
            msg += '    - '
            msg += '{} unidades foram atualizadas.\n'.format(len(atualizadas))
            log.info('    {}'.format(atualizadas))

        if novas:
            msg += '    - '
            msg += '{} unidades foram adicionadas.\n'.format(len(novas))

        if errors:
            msg += 'Ocorreram {} erros de importação:\n'.format(len(errors))
            for error in errors:
                msg += '    - '
                msg += 'Unidade: {:.30}'.format(error['nome_unidade'])
                msg += ' | {} | {}/{}\n'.format(error['erro'],
                                                error['data']['uf'],
                                                error['data']['municipio'])

        log.info(msg)

    def _update_from_dict(self, data):
        """Update a 'Unidade Prisional' based on its name and return it.

        The 'data' attribute is a dictionary with the necessary fields to
        generate a new Unidade Prisional.
        """

        self.sigla_unidade = data['sigla_unidade']
        self.tipo_logradouro = data['tipo_logradouro']
        self.nome_logradouro = data['nome_logradouro']
        if isinstance(data['numero'], int):
            self.numero = data['numero']
        else:
            try:
                self.numero = int(data['numero'])
            except:
                self.numero = None
        self.complemento = data['complemento']
        self.bairro = data['bairro']
        self.municipio = Cidade.objects.get(nome=data['municipio'],
                                            estado=data['uf'])
        self.uf = data['uf']
        self.cep = data['cep']
        if isinstance(data['ddd'], int):
            self.ddd = data['ddd']
        else:
            try:
                self.ddd = int(data['ddd'])
            except:
                self.ddd = None
        if isinstance(data['telefone'], int):
            self.telefone = data['telefone']
        else:
            try:
                self.telefone = int(data['telefone'])
            except:
                self.telefone = None
        self.email = data['email']

class BaseMJ(models.Model):
    """Manage the multiple versions of MJ Infopen raw database."""

    ano = models.IntegerField(choices=YEAR_CHOICES, default=current_year)
    mes = models.CharField(verbose_name='Mês', max_length=40,
                           choices=MONTH_CHOICES, default=current_month)
    arquivo = models.FileField(upload_to='base_bruta_mj/',
                               validators=[check_filetype])
    salvo_em = models.DateTimeField(verbose_name='Salvo em', auto_now_add=True)

    def __str__(self):
        return "{}/{}".format(self.mes, self.ano)

    class Meta:
        verbose_name = 'Base bruta MJ'
        verbose_name_plural = 'Bases brutas MJ'

class ArquivoBaseCarceropolis(models.Model):
    """Manage the multiple raw versions of the cleaned MJ/Infopen database."""

    ano = models.IntegerField(choices=YEAR_CHOICES, default=current_year)
    mes = models.CharField(verbose_name='Mês', max_length=40,
                           choices=MONTH_CHOICES, default=current_month)
    arquivo = models.FileField(upload_to='base_bruta_carceropolis/',
                               validators=[check_filetype])
    salvo_em = models.DateTimeField(verbose_name='Salvo em', auto_now_add=True)

    def __str__(self):
        return "{}/{}".format(self.mes, self.ano)

    class Meta:
        verbose_name = u'Base bruta Carcerópolis'
        verbose_name_plural = u'Bases brutas Carcerópolis'
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.md', which is part of this source code package.
#

import json
import yaml

from kubernetes.K8sConfig import K8sConfig
from kubernetes.K8sExceptions import *
from kubernetes.models.v1.BaseUrls import BaseUrls
from kubernetes.models.v1.DeleteOptions import DeleteOptions
from kubernetes.utils import HttpRequest, is_valid_dict, str_to_class

VALID_K8s_OBJS = [
    'Deployment',
    'PersistentVolume',
    'PersistentVolumeClaim',
    'Pod',
    'ReplicaSet',
    'ReplicationController',
    'Secret',
    'Service',
    'Volume'
]

class K8sObject(object):
    def __init__(self, config=None, obj_type=None, name=None):
        super(K8sObject, self).__init__()

        if config is not None and not isinstance(config, K8sConfig):
            raise SyntaxError('K8sObject: config: [ {0} ] must be of type K8sConfig.'.format(config.__class__.__name__))
        if config is None:
            config = K8sConfig()
        self.config = config

        if obj_type not in VALID_K8s_OBJS:
            valid = ", ".join(VALID_K8s_OBJS)
            raise SyntaxError('K8sObject: obj_type: [ {0} ] must be in: [ {1} ]'.format(obj_type, valid))
        self.obj_type = obj_type

        self.model = str_to_class(obj_type)
        self.name = name

        try:
            urls = BaseUrls(api_version=self.config.version, namespace=self.config.namespace)
            self.base_url = urls.get_base_url(object_type=obj_type)
        except:
            raise Exception('Could not set BaseUrl for type: [ {0} ]'.format(obj_type))

    def __str__(self):
        return "{}".format(self.model.serialize())

    def __eq__(self, other):
        # see https://github.com/kubernetes/kubernetes/blob/release-1.3/docs/design/identifiers.md
        if isinstance(other, self.__class__):
            return self.config.namespace == other.config.namespace and self.name == other.name
        return NotImplemented

    # ------------------------------------------------------------------------------------- get

    def get_annotation(self, k=None):
        if k in self.model.metadata.annotations:
            return self.model.metadata.annotations[k]
        return None

    def get_label(self, k=None):
        if k in self.model.metadata.labels:
            return self.model.metadata.labels[k]
        return None

    # ------------------------------------------------------------------------------------- add

    def add_annotation(self, k=None, v=None):
        anns = self.model.metadata.annotations
        if anns is None:
            anns = {}
        anns.update({k: str(v)})
        self.model.metadata.annotations = anns
        return self

    def add_label(self, k=None, v=None):
        labels = self.model.metadata.labels
        if labels is None:
            labels = {}
        labels.update({k: v})
        self.model.metadata.labels = labels
        return self

    # ------------------------------------------------------------------------------------- del

    def del_annotation(self, k=None):
        orig = self.model.metadata.annotations
        if k in orig:
            orig.pop(k)
            self.model.metadata.annotations = orig
        return self

    def del_label(self, k=None):
        orig = self.model.metadata.labels
        if k in orig:
            orig.pop(k)
            self.model.metadata.labels = orig
        return self

    # ------------------------------------------------------------------------------------- annotations

    @property
    def annotations(self):
        return self.model.metadata.annotations

    @annotations.setter
    def annotations(self, anns=None):
        self.model.metadata.annotations = anns

    # ------------------------------------------------------------------------------------- labels

    @property
    def labels(self):
        return self.model.metadata.labels

    @labels.setter
    def labels(self, labels=None):
        self.model.metadata.labels = labels

    # ------------------------------------------------------------------------------------- name

    @property
    def name(self):
        return self.model.metadata.name

    @name.setter
    def name(self, name=None):
        self.model.metadata.labels['name'] = name
        self.model.metadata.name = name

    # ------------------------------------------------------------------------------------- serialize

    def serialize(self):
        return self.model.serialize()

    def as_json(self):
        data = self.serialize()
        dump = json.dumps(data, indent=4)
        return dump

    def as_yaml(self):
        data = self.serialize()
        dump = yaml.dump(data, default_flow_style=False)
        return dump

    # ------------------------------------------------------------------------------------- remote API calls

    def request(self, method='GET', host=None, url=None, auth=None, cert=None,
                data=None, token=None, ca_cert=None, ca_cert_data=None):

        host = self.config.api_host if host is None else host
        url = self.base_url if url is None else url
        auth = self.config.auth if auth is None else auth
        cert = self.config.cert if cert is None else cert
        token = self.config.token if token is None else token
        ca_cert = self.config.ca_cert if ca_cert is None else ca_cert
        ca_cert_data = self.config.ca_cert_data if ca_cert_data is None else ca_cert_data

        r = HttpRequest(
            method=method,
            host=host,
            url=url,
            auth=auth,
            cert=cert,
            ca_cert=ca_cert,
            ca_cert_data=ca_cert_data,
            data=data,
            token=token
        )

        try:
            return r.send()
        except IOError as err:
            raise BadRequestException('K8sObject: IOError: {0}'.format(err))

    def list(self):
        state = self.request(method='GET')
        if not state.get('status'):
            raise Exception('K8sObject: Could not fetch list of objects of type: [ {0} ]'.format(self.obj_type))
        if not state.get('success'):
            status = state.get('status', '')
            state_data = state.get('data', dict())
            reason = state_data['message'] if 'message' in state_data else state_data
            message = 'K8sObject: CREATE failed : HTTP {0} : {1}'.format(status, reason)
            if int(status) == 401:
                raise UnauthorizedException(message)
            if int(status) == 409:
                raise AlreadyExistsException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)
        return state.get('data', dict()).get('items', list())

    def get_model(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to fetch the object.'.format(self.name))

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)
        state = self.request(method='GET', url=url)

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: GET [ {0}:{1} ] failed: HTTP {2} : {3} '.format(self.obj_type, self.name, status,
                                                                                reason)
            raise NotFoundException(message)

        model = state.get('data')
        return model

    def get_with_params(self, data=None):
        if not is_valid_dict(data):
            raise SyntaxError('K8sObject.get_with_params(): data: [ {0} ] is invalid.'.format(data))
        url = '{base}'.format(base=self.base_url)
        state = self.request(method='GET', url=url, data=data)
        return state.get('data', None).get('items', list())

    def create(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to CREATE the object.'.format(self.name))

        # HTTP 500 : resourceVersion may not be set on objects to be created
        if self.model.metadata.resource_version is not None:
            self.model.metadata.resource_version = None

        url = '{base}'.format(base=self.base_url)
        post_data = self.serialize()
        state = self.request(method='POST', url=url, data=post_data)

        if not state.get('success'):
            status = state.get('status', '')
            state_data = state.get('data', dict())
            reason = state_data['message'] if 'message' in state_data else state_data
            message = 'K8sObject: CREATE failed : HTTP {0} : {1} : {2}'.format(status, reason, post_data)
            if int(status) == 401:
                raise UnauthorizedException(message)
            if int(status) == 409:
                raise AlreadyExistsException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)
        return self

    def update(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to UPDATE the object.'.format(self.name))

        self.model.metadata.strip(self.model.kind)  # strip server-generated metadata before posting updates

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)
        state = self.request(method='PUT', url=url, data=self.serialize())

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: UPDATE failed: HTTP {0} : {1}'.format(status, reason)
            if int(status) == 404:
                raise NotFoundException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)

        return self

    def delete(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to DELETE the object.'.format(self.name))

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)
        state = self.request(method='DELETE', url=url, data=DeleteOptions().serialize())

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: DELETE failed: HTTP {0} : {1}'.format(status, reason)
            if int(status) == 404:
                raise NotFoundException(message)
            raise BadRequestException(message)

        return self
<commit_msg>K8sObject: add support for ‘orphan’, ie. cascade on delete<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.md', which is part of this source code package.
#

import json
import yaml

from kubernetes.K8sConfig import K8sConfig
from kubernetes.K8sExceptions import *
from kubernetes.models.v1.BaseUrls import BaseUrls
from kubernetes.models.v1.DeleteOptions import DeleteOptions
from kubernetes.utils import HttpRequest, is_valid_dict, str_to_class

VALID_K8s_OBJS = [
    'Deployment',
    'PersistentVolume',
    'PersistentVolumeClaim',
    'Pod',
    'ReplicaSet',
    'ReplicationController',
    'Secret',
    'Service',
    'Volume'
]

class K8sObject(object):
    def __init__(self, config=None, obj_type=None, name=None):
        super(K8sObject, self).__init__()

        if config is not None and not isinstance(config, K8sConfig):
            raise SyntaxError('K8sObject: config: [ {0} ] must be of type K8sConfig.'.format(config.__class__.__name__))
        if config is None:
            config = K8sConfig()
        self.config = config

        if obj_type not in VALID_K8s_OBJS:
            valid = ", ".join(VALID_K8s_OBJS)
            raise SyntaxError('K8sObject: obj_type: [ {0} ] must be in: [ {1} ]'.format(obj_type, valid))
        self.obj_type = obj_type

        self.model = str_to_class(obj_type)
        self.name = name

        try:
            urls = BaseUrls(api_version=self.config.version, namespace=self.config.namespace)
            self.base_url = urls.get_base_url(object_type=obj_type)
        except:
            raise Exception('Could not set BaseUrl for type: [ {0} ]'.format(obj_type))

    def __str__(self):
        return "{}".format(self.model.serialize())

    def __eq__(self, other):
        # see https://github.com/kubernetes/kubernetes/blob/release-1.3/docs/design/identifiers.md
        if isinstance(other, self.__class__):
            return self.config.namespace == other.config.namespace and self.name == other.name
        return NotImplemented

    # ------------------------------------------------------------------------------------- get

    def get_annotation(self, k=None):
        if k in self.model.metadata.annotations:
            return self.model.metadata.annotations[k]
        return None

    def get_label(self, k=None):
        if k in self.model.metadata.labels:
            return self.model.metadata.labels[k]
        return None

    # ------------------------------------------------------------------------------------- add

    def add_annotation(self, k=None, v=None):
        anns = self.model.metadata.annotations
        if anns is None:
            anns = {}
        anns.update({k: str(v)})
        self.model.metadata.annotations = anns
        return self

    def add_label(self, k=None, v=None):
        labels = self.model.metadata.labels
        if labels is None:
            labels = {}
        labels.update({k: v})
        self.model.metadata.labels = labels
        return self

    # ------------------------------------------------------------------------------------- del

    def del_annotation(self, k=None):
        orig = self.model.metadata.annotations
        if k in orig:
            orig.pop(k)
            self.model.metadata.annotations = orig
        return self

    def del_label(self, k=None):
        orig = self.model.metadata.labels
        if k in orig:
            orig.pop(k)
            self.model.metadata.labels = orig
        return self

    # ------------------------------------------------------------------------------------- annotations

    @property
    def annotations(self):
        return self.model.metadata.annotations

    @annotations.setter
    def annotations(self, anns=None):
        self.model.metadata.annotations = anns

    # ------------------------------------------------------------------------------------- labels

    @property
    def labels(self):
        return self.model.metadata.labels

    @labels.setter
    def labels(self, labels=None):
        self.model.metadata.labels = labels

    # ------------------------------------------------------------------------------------- name

    @property
    def name(self):
        return self.model.metadata.name

    @name.setter
    def name(self, name=None):
        self.model.metadata.labels['name'] = name
        self.model.metadata.name = name

    # ------------------------------------------------------------------------------------- serialize

    def serialize(self):
        return self.model.serialize()

    def as_json(self):
        data = self.serialize()
        dump = json.dumps(data, indent=4)
        return dump

    def as_yaml(self):
        data = self.serialize()
        dump = yaml.dump(data, default_flow_style=False)
        return dump

    # ------------------------------------------------------------------------------------- remote API calls

    def request(self, method='GET', host=None, url=None, auth=None, cert=None,
                data=None, token=None, ca_cert=None, ca_cert_data=None):

        host = self.config.api_host if host is None else host
        url = self.base_url if url is None else url
        auth = self.config.auth if auth is None else auth
        cert = self.config.cert if cert is None else cert
        token = self.config.token if token is None else token
        ca_cert = self.config.ca_cert if ca_cert is None else ca_cert
        ca_cert_data = self.config.ca_cert_data if ca_cert_data is None else ca_cert_data

        r = HttpRequest(
            method=method,
            host=host,
            url=url,
            auth=auth,
            cert=cert,
            ca_cert=ca_cert,
            ca_cert_data=ca_cert_data,
            data=data,
            token=token
        )

        try:
            return r.send()
        except IOError as err:
            raise BadRequestException('K8sObject: IOError: {0}'.format(err))

    def list(self):
        state = self.request(method='GET')
        if not state.get('status'):
            raise Exception('K8sObject: Could not fetch list of objects of type: [ {0} ]'.format(self.obj_type))
        if not state.get('success'):
            status = state.get('status', '')
            state_data = state.get('data', dict())
            reason = state_data['message'] if 'message' in state_data else state_data
            message = 'K8sObject: CREATE failed : HTTP {0} : {1}'.format(status, reason)
            if int(status) == 401:
                raise UnauthorizedException(message)
            if int(status) == 409:
                raise AlreadyExistsException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)
        return state.get('data', dict()).get('items', list())

    def get_model(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to fetch the object.'.format(self.name))

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)
        state = self.request(method='GET', url=url)

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: GET [ {0}:{1} ] failed: HTTP {2} : {3} '.format(self.obj_type, self.name, status,
                                                                                reason)
            raise NotFoundException(message)

        model = state.get('data')
        return model

    def get_with_params(self, data=None):
        if not is_valid_dict(data):
            raise SyntaxError('K8sObject.get_with_params(): data: [ {0} ] is invalid.'.format(data))
        url = '{base}'.format(base=self.base_url)
        state = self.request(method='GET', url=url, data=data)
        return state.get('data', None).get('items', list())

    def create(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to CREATE the object.'.format(self.name))

        # HTTP 500 : resourceVersion may not be set on objects to be created
        if self.model.metadata.resource_version is not None:
            self.model.metadata.resource_version = None

        url = '{base}'.format(base=self.base_url)
        post_data = self.serialize()
        state = self.request(method='POST', url=url, data=post_data)

        if not state.get('success'):
            status = state.get('status', '')
            state_data = state.get('data', dict())
            reason = state_data['message'] if 'message' in state_data else state_data
            message = 'K8sObject: CREATE failed : HTTP {0} : {1} : {2}'.format(status, reason, post_data)
            if int(status) == 401:
                raise UnauthorizedException(message)
            if int(status) == 409:
                raise AlreadyExistsException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)
        return self

    def update(self):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to UPDATE the object.'.format(self.name))

        self.model.metadata.strip(self.model.kind)  # strip server-generated metadata before posting updates

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)
        state = self.request(method='PUT', url=url, data=self.serialize())

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: UPDATE failed: HTTP {0} : {1}'.format(status, reason)
            if int(status) == 404:
                raise NotFoundException(message)
            if int(status) == 422:
                raise UnprocessableEntityException(message)
            raise BadRequestException(message)

        return self

    def delete(self, orphan=False):
        if self.name is None:
            raise SyntaxError('K8sObject: name: [ {0} ] must be set to DELETE the object.'.format(self.name))

        url = '{base}/{name}'.format(base=self.base_url, name=self.name)

        delete_opts = DeleteOptions()
        delete_opts.orphan_dependents = orphan

        state = self.request(method='DELETE', url=url, data=delete_opts.serialize())

        if not state.get('success'):
            status = state.get('status', '')
            reason = state.get('data', dict()).get('message', None)
            message = 'K8sObject: DELETE failed: HTTP {0} : {1}'.format(status, reason)
            if int(status) == 404:
                raise NotFoundException(message)
            raise BadRequestException(message)

        return self
<|endoftext|>