<commit_before>import re
from operator import attrgetter

from populous.exceptions import GenerationError

# match a variable with attributes ($var.attr), except if it's escaped
VARS_REGEX = re.compile(r"(?<!\\)\$(?P<var>\w[\w.]*(?<!\.))+")
SUBSTITUTE = "{\g<var>}"

def parse_vars(value):
    if not isinstance(value, str):
        return value

    if "$" not in value:
        # fast path
        return value

    match = re.search(VARS_REGEX, value)

    if not match:
        return value.replace('\$', '$')

    # if the match is spanning over the entire string,
    # this is a value expression
    if match.span() == (0, len(value)):
        return ValueExpression(match.group(1))

    # otherwise we should use a template expression
    return TemplateExpression(value)

class Expression(object):

    def evaluate(self, **vars):
        raise NotImplementedError()

class ValueExpression(Expression):

    def __init__(self, var):
        self.var, _, attrs = var.partition('.')
        if attrs:
            self.attrgetter = attrgetter(attrs)
        else:
            self.attrgetter = None
        self.attrs = attrs

    def evaluate(self, **vars_):
        try:
            var = vars_[self.var]
        except KeyError:
            raise GenerationError(
                "Variable '{}' not found.".format(self.var)
            )
        if self.attrgetter:
            try:
                return self.attrgetter(var)
            except AttributeError as e:
                # this message is not very informative ('type' objects has no
                # attribute 'attr'), but we cannot do much better without
                # inspecting the calling frame
                raise GenerationError(e.message)
        else:
            return var

class TemplateExpression(Expression):

    def __init__(self, value=""):
        # escape present '{' and '}'
        value = value.replace('{', '{{').replace('}', '}}')
        template = re.sub(VARS_REGEX, SUBSTITUTE, value)
        template = template.replace('\$', '$')
        self.template = template

    def __str__(self):
        return self.evaluate()

    def evaluate(self, **vars_):
        try:
            return self.template.format(**vars_)
        except KeyError as e:
            raise GenerationError(
                "Variable '{}' not found.".format(e.args[0])
            )
        except AttributeError as e:
            # this message is not very informative ('type' objects has no
            # attribute 'attr'), but we cannot do much better without
            # inspecting the calling frame
            raise GenerationError(e.message)
<commit_msg>Use Jinja for expressions & templates<commit_after>import re
from operator import attrgetter

import jinja2

from populous.exceptions import GenerationError
from populous.exceptions import ValidationError

jinja_env = jinja2.Environment()
jinja_env.undefined = jinja2.StrictUndefined

VAR_REGEX = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$')

def parse_vars(value):
    if not value or not isinstance(value, basestring):
        return value

    if value[0] == '$':
        # find out if it is a '$var` or `$(expr)`
        if len(value) > 1 and value[1] == '(':
            if value[-1] != ')':
                raise ValidationError(
                    "Error parsing '{}': Missing ')'".format(value)
                )
            return JinjaValueExpression(value[2:-1])

        if not VAR_REGEX.match(value[1:]):
            raise ValidationError(
                "Error parsing '{}': This is not a valid value expression. "
                "You should either escape the $ with a '\\', or use the "
                "template syntax ('... {{{{ var }}}} ...')."
                .format(value)
            )
        return ValueExpression(value[1:])
    elif value.startswith('\$'):
        # the $ was escaped, remove the escape char
        value = value[1:]

    if '{{' in value or '{%' in value:
        # this might be a template expression
        # even if it's not, using a TemplateExpression will return
        # the same result and be almost as fast
        return TemplateExpression(value)

    return value

class Expression(object):

    def evaluate(self, **vars):
        raise NotImplementedError()

class ValueExpression(Expression):

    def __init__(self, value):
        self.value = value
        self.var, _, attrs = value.partition('.')
        if attrs:
            self.attrgetter = attrgetter(attrs)
        else:
            self.attrgetter = None
        self.attrs = attrs

    def evaluate(self, **vars_):
        try:
            var = vars_[self.var]
        except KeyError:
            raise GenerationError(
                "Error generating value '${}': '{}' is undefined"
                .format(self.value, self.var)
            )
        if self.attrgetter:
            try:
                return self.attrgetter(var)
            except AttributeError as e:
                # this message is not very informative ('type' object has no
                # attribute 'attr'), but we cannot do much better without
                # inspecting the calling frame
                raise GenerationError(
                    "Error generating value '${}': {}"
                    .format(self.value, e.message)
                )
        else:
            return var

class JinjaValueExpression(Expression):

    def __init__(self, value):
        self.value = value
        try:
            self.expression = jinja_env.compile_expression(
                value, undefined_to_none=False
            )
        except jinja2.TemplateError as e:
            raise ValidationError(
                "Error parsing '$({})': Invalid Jinja2 expression ({})"
                .format(value, e)
            )

    def evaluate(self, **vars_):
        try:
            value = self.expression(vars_)
            if isinstance(value, jinja2.Undefined):
                # trigger a UndefinedError
                str(value)
            return value
        except jinja2.UndefinedError as e:
            raise GenerationError(
                "Error generating value '$({})': {}".format(self.value, e)
            )

class TemplateExpression(Expression):

    def __init__(self, value=""):
        self.value = value
        self.template = jinja_env.from_string(value)

    def evaluate(self, **vars_):
        try:
            return self.template.render(vars_)
        except jinja2.UndefinedError as e:
            raise GenerationError(
                "Error generating template '{}': {}".format(self.value, e)
            )
<|endoftext|>


<commit_before>from fluentopt import RandomSearch
import numpy as np

def sampler(rng):
    return rng.uniform(-1, 1)

def feval(x):
    return (x ** 2 - 2)

opt = RandomSearch(sampler=sampler)
n_iter = 1000

for _ in range(n_iter):
    x = opt.suggest()
    y = feval(x)
    opt.update(x=x, y=y)

idx = np.argmin(opt.output_history_)
best_input = opt.input_history_[idx]
best_output = opt.output_history_[idx]
print('best input : {:.2f}, best output : {:.2f}'.format(best_input, best_output))
<commit_msg>update the random search example<commit_after>"""
======================================================
Plotting random search behavior
======================================================

Show the evolution of the current min with random search
"""
from fluentopt import RandomSearch
import numpy as np
import matplotlib.pyplot as plt
from scipy import minimum

np.random.seed(42)

def sampler(rng):
    return rng.uniform(-1, 1)

def feval(x):
    return (x ** 2 - 2)

opt = RandomSearch(sampler=sampler)
n_iter = 100

for _ in range(n_iter):
    x = opt.suggest()
    y = feval(x)
    opt.update(x=x, y=y)

idx = np.argmin(opt.output_history_)
best_input = opt.input_history_[idx]
best_output = opt.output_history_[idx]
print('best input : {:.2f}, best output : {:.2f}'.format(best_input, best_output))
iters = np.arange(len(opt.output_history_))
plt.plot(iters, minimum.accumulate(opt.output_history_))
plt.xlabel('iteration')
plt.ylabel('current $min_x({x**2-2})$')
plt.show()
<|endoftext|>


<commit_before># Authors: Denis Engemann <d.engemann@fz-juelich.de>
#
# License: BSD (3-clause)

import pylinkparse as plp

fname = '../pylinkparse/tests/data/test_raw.asc'

raw = plp.Raw(fname)

raw.plot_calibration(title='9-Point Calibration')

events = plp.find_custom_events(raw, 'user-event', event_id=1)

raw.plot_heatmap(start=10., stop=85.)

tmin, tmax, event_id = -1.5, 2.0, 1

epochs = plp.Epochs(raw, events=events, event_id=event_id, tmin=tmin,
                    tmax=tmax)
<commit_msg>ENH: improve epochs constructor<commit_after># Authors: Denis Engemann <d.engemann@fz-juelich.de>
#
# License: BSD (3-clause)

import pylinkparse as plp

fname = '../pylinkparse/tests/data/test_raw.asc'

raw = plp.Raw(fname)

raw.plot_calibration(title='9-Point Calibration')

events = plp.find_custom_events(raw, 'user-event', event_id=1)

raw.plot_heatmap(start=10., stop=85.)

tmin, tmax, event_id = -0.5, 1.5, 1

epochs = plp.Epochs(raw, events=events, event_id=event_id, tmin=tmin,
                    tmax=tmax)
<|endoftext|>


<commit_before>from django.test import TestCase
from djangorestframework.compat import RequestFactory
from pygments_api import views
import tempfile, shutil, json

class TestPygmentsExample(TestCase):

    def setUp(self):
        self.factory = RequestFactory()
        self.temp_dir = tempfile.mkdtemp()
        views.HIGHLIGHTED_CODE_DIR = self.temp_dir

    def tearDown(self):
        try:
            shutil.rmtree(self.temp_dir)
        except:
            pass

    def test_get_to_root(self):
        '''Just do a get on the base url'''
        request = self.factory.get('/pygments')
        view = views.PygmentsRoot.as_view()
        response = view(request)
        self.assertEqual(response.status_code, 200)

    def test_snippets_datetime_sorted(self):
        '''Pygments examples should be datetime sorted'''
        locations = []
        for snippet in 'abcdefghijk':
            form_data = {'code': '%s' % snippet, 'style':'friendly', 'lexer':'python'}
            request = self.factory.post('/pygments', data=form_data)
            view = views.PygmentsRoot.as_view()
            response = view(request)
            locations.append(response.items()[2][1])
        request = self.factory.get('/pygments')
        view = views.PygmentsRoot.as_view()
        response = view(request)
        response_locations = json.loads(response.content)
        self.assertEquals(locations, response_locations)

        <commit_msg>fix compat with python 2.5<commit_after>from django.test import TestCase
from djangorestframework.compat import RequestFactory
from pygments_api import views
import tempfile, shutil

try:
    import json
except ImportError:
    import simplejson as json

class TestPygmentsExample(TestCase):

    def setUp(self):
        self.factory = RequestFactory()
        self.temp_dir = tempfile.mkdtemp()
        views.HIGHLIGHTED_CODE_DIR = self.temp_dir

    def tearDown(self):
        try:
            shutil.rmtree(self.temp_dir)
        except:
            pass

    def test_get_to_root(self):
        '''Just do a get on the base url'''
        request = self.factory.get('/pygments')
        view = views.PygmentsRoot.as_view()
        response = view(request)
        self.assertEqual(response.status_code, 200)

    def test_snippets_datetime_sorted(self):
        '''Pygments examples should be datetime sorted'''
        locations = []
        for snippet in 'abcdefghijk':
            form_data = {'code': '%s' % snippet, 'style':'friendly', 'lexer':'python'}
            request = self.factory.post('/pygments', data=form_data)
            view = views.PygmentsRoot.as_view()
            response = view(request)
            locations.append(response.items()[2][1])
        request = self.factory.get('/pygments')
        view = views.PygmentsRoot.as_view()
        response = view(request)
        response_locations = json.loads(response.content)
        self.assertEquals(locations, response_locations)

        <|endoftext|>


<commit_before>from gi.repository import Zeitgeist, Gtk
log = Zeitgeist.Log.get_default()

def callback (log, result, data):
    events = log.get_events_finish(result)
    print len(events)
    #for event in events:
    #    if event:
    #        print "==="
    Gtk.main_quit()

log.get_events([x for x in xrange(200, 222)], None, callback, None)
Gtk.main()
<commit_msg>fix python example<commit_after>from gi.repository import Zeitgeist, Gtk
log = Zeitgeist.Log.get_default()

def callback (log, result, data):
    events = log.get_events_finish(result)
    print len(events)
    for event in events:
        if event:
            print event.num_subjects()
    Gtk.main_quit()

log.get_events([x for x in xrange(200, 222)], None, callback, None)
Gtk.main()
<|endoftext|>


<commit_before>import traceback
from submissions.LaMartina import state_crime

class DataFrame:
    data = []
    feature_names = []
    target = []
    target_names = []

crimes = DataFrame()

'''
Extract data from the CORGIS state_crime.
'''
joint = {}

crime = state_crime.get_all_crimes()

for c in crime:
    try:
        #if c['State'] == 'Alabama':
        stateyear = c['State'] + str(c['Year'])
        pop = c['Data']['Population']
        murder = c['Data']['Totals']['Violent']['Murder']
        rape = c['Data']['Totals']['Violent']['Rape']
        burg = c['Data']['Totals']['Property']['Burglary']
        joint[stateyear] = {}
        joint[stateyear]['Population'] = pop
        joint[stateyear]['Murder Numbers'] = murder
        joint[stateyear]['Rape Numbers'] = rape
        joint[stateyear]['Burglary Numbers'] = burg
    except:
        traceback.print_exc()

crimes.data = []

'''
Build the input frame, row by row.
'''
for port in joint:
    # choose the input values
    crimes.data.append([
        #port,
        joint[port]['Population'],
        joint[port]['Rape Numbers'],
        joint[port]['Burglary Numbers'],
    ])
crimes.feature_names = [
    'Population',
    'Rape Numbers',
    'Burglary Numbers',
]
'''
Build the target list,
one entry for each row in the input frame.

The Naive Bayesian network is a classifier,
i.e. it sorts data points into bins.
The best it can do to estimate a continuous variable
is to break the domain into segments, and predict
the segment into which the variable's value will fall.
In this example, I'm breaking Trump's % into two
arbitrary segments.
'''
crimes.target = []

def murderTarget(murdernum):
    if murdernum > 600:
        return 1
    return 0

for cri in joint:
    # choose the target
    c = murderTarget(joint[cri]['Murder Numbers'])
    crimes.target.append(c)

crimes.target_names = [
    'Murders <= 600',
    'Murders >  600',
]

Examples = {
    'Crimes': crimes,
}<commit_msg>Got the data frame to work but now need to mess with input data so that it consistently gets better than 50%<commit_after>import traceback
from submissions.LaMartina import state_crime

class DataFrame:
    data = []
    feature_names = []
    target = []
    target_names = []

crimes = DataFrame()

'''
Extract data from the CORGIS state_crime.
'''
joint = {}

crime = state_crime.get_all_crimes()

for c in crime:
    try:
        #if c['State'] == 'Alabama':
        stateyear = c['State'] + str(c['Year'])
        pop = c['Data']['Population']
        murder = c['Data']['Totals']['Violent']['Murder']
        rape = c['Data']['Totals']['Violent']['Rape']
        burg = c['Data']['Totals']['Property']['Burglary']
        joint[stateyear] = {}
        joint[stateyear]['Population'] = pop
        joint[stateyear]['Murder Numbers'] = murder
        joint[stateyear]['Rape Numbers'] = rape
        joint[stateyear]['Burglary Numbers'] = burg
    except:
        traceback.print_exc()

crimes.data = []

'''
Build the input frame, row by row.
'''
for port in joint:
    # choose the input values
    crimes.data.append([
        #port,
        joint[port]['Population'],
        joint[port]['Rape Numbers'],
        joint[port]['Burglary Numbers'],
    ])
crimes.feature_names = [
    'Population',
    'Rape Numbers',
    'Burglary Numbers',
]
'''
Build the target list,
one entry for each row in the input frame.

The Naive Bayesian network is a classifier,
i.e. it sorts data points into bins.
The best it can do to estimate a continuous variable
is to break the domain into segments, and predict
the segment into which the variable's value will fall.
In this example, I'm breaking Trump's % into two
arbitrary segments.
'''
crimes.target = []

def murderTarget(murdernum):
    if murdernum > 800:
        return 1
    return 0

for cri in joint:
    # choose the target
    c = murderTarget(joint[cri]['Murder Numbers'])
    crimes.target.append(c)

crimes.target_names = [
    'Murders <= 800',
    'Murders >  800',
]

Examples = {
    'Crimes': {
        'frame': crimes,
    },
}<|endoftext|>


<commit_before>#!/usr/bin/python

import sys

bad_PDUs_hex = [
	'0a 0a 0a',
]

bad_PDUs = [''.join([chr(int(c, 0x10)) for c in s.split(' ')]) for s in bad_PDUs_hex]

if __name__ == '__main__':
	def print_usage():
		print("Usage: %s <index> | length" % sys.argv[0])
		print("    %s <index>: output the bad PDU at the 1-based index specified" % sys.argv[0])
		print("    %s length: output the total number of bad PDUs" % sys.argv[0])
		sys.exit(1)

	if len(sys.argv) != 2:
		print_usage()

	if sys.argv[1] == 'length':
		print len(bad_PDUs)
		sys.exit()

	try:
		index = int(sys.argv[1])
	except ValueError:
		print_usage()

	if index < 1:
		print_usage()
	if index > len(bad_PDUs):
		sys.exit("Error: index %d is greater than the total number of PDUs (%d)" % (index, len(bad_PDUs)))

	print bad_PDUs[index - 1]
<commit_msg>start work on making erroneous PDUs<commit_after>#!/usr/bin/python

import sys

bad_PDUs_hex = [
	# generic errors
	'01 02 00 00 00 00 00 08', # invalid protocol version = 1, otherwise valid Reset Query
	'00 ff 00 00 00 00 00 08', # invalid PDU type, otherwise valid Reset Query
	'00 00 f0 0f ff ff ff ff', # length too long TODO: this should try to send 0xFFFFFFFF bytes, maybe
	# TODO: PDUs with truncated headers?

	# Serial Notify
	'00 00 f0 0f 00 00 00 08', # length too short
	'00 00 f0 0f 00 00 00 0b f0 0f f0', # length too short
	'00 00 f0 0f 00 00 00 0d f0 0f f0 0f f0', # length too long

	# Serial Query
	'00 01 f0 0f 00 00 00 08', # length too short
	'00 01 f0 0f 00 00 00 0b f0 0f f0', # length too short
	'00 01 f0 0f 00 00 00 0d f0 0f f0 0f f0', # length too long

	# Reset Query
	'00 02 00 00 00 00 00 09 f0', # length too long

	# TODO: Cache Response

	# TODO: IPv4 Prefix

	# TODO: IPv6 Prefix

	# TODO: End of Data

	# TODO: Cache Reset

	# Error Report
	'00 0a 00 01 ' + '00 00 00 11 ' + '00 00 00 00 ' + '00 00 00 00 ' + 'f0', # Internal Error with internal lengths less than total length
	'00 0a 00 01 ' + '00 00 00 10 ' + '00 00 00 00 ' + '00 00 00 01', # Internal Error with internal lengths greater than total length
]

bad_PDUs = [''.join([chr(int(c, 0x10)) for c in s.split(' ')]) for s in bad_PDUs_hex]

if __name__ == '__main__':
	def print_usage():
		print("Usage: %s <index> | length" % sys.argv[0])
		print("    %s <index>: output the bad PDU at the 1-based index specified" % sys.argv[0])
		print("    %s length: output the total number of bad PDUs" % sys.argv[0])
		sys.exit(1)

	if len(sys.argv) != 2:
		print_usage()

	if sys.argv[1] == 'length':
		print len(bad_PDUs)
		sys.exit()

	try:
		index = int(sys.argv[1])
	except ValueError:
		print_usage()

	if index < 1:
		print_usage()
	if index > len(bad_PDUs):
		sys.exit("Error: index %d is greater than the total number of PDUs (%d)" % (index, len(bad_PDUs)))

	print bad_PDUs[index - 1]
<|endoftext|>


<commit_before># Copyright (c) Sunlight Labs, 2012 under the terms and conditions
# of the LICENSE file.

"""
.. module:: sunlight.services.congress
    :synopsis: Sunlight Congress API Implementation

Sunlight Congress API Implementation inside ``python-sunlight``.
"""

import sunlight.service
import json

service_url = "http://services.sunlightlabs.com/api/"

class Congress(sunlight.service.Service):
    """
    Bindings into the `Sunlight Congress <http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_
    project. Keep in mind that this is all a very thin wrapper around the API
    it's self, so most of the docuementation is best found on the API doc site
    linked above.
    """

    """
    Get a legislator by a bit of known information. If the query returns more
    then one legislator, this method will throw an error, as is the case with
    the server not finding any information.

    To read more about this, check out the
    `docs <http://services.sunlightlabs.com/docs/congressapi/legislators.get(List)/>`_.
    """
    def legislator( self, **kwargs ):
        return self.get( "legislators.get", **kwargs )

    """
    Get a list of legislators by bits of known information. This method is able
    to return more then one ( a list would be rather lonely without some other
    things in it ), unlike the :func:`sunlight.services.congress.Congress.legislator`
    method.
    """
    def legislators( self, **kwargs ):
        return self.get( "legislators.getList", **kwargs )

    # API impl methods below

    def _get_url( self, obj, apikey, **kwargs ):
        ret = "%s/%s.json?apikey=%s" % (
            service_url,
            obj,
            apikey
        )
        for arg in kwargs:
            ret += "&%s=%s" % ( arg, kwargs[arg] )
        return ret

    def _decode_response( self, response ):
        return json.loads( response )

<commit_msg>flesh out sunlight.congress<commit_after># Copyright (c) Sunlight Labs, 2012 under the terms and conditions
# of the LICENSE file.

"""
.. module:: sunlight.services.congress
    :synopsis: Sunlight Congress API Implementation

Sunlight Congress API Implementation inside ``python-sunlight``.
"""

import sunlight.service
import urllib
import json

service_url = "http://services.sunlightlabs.com/api/"

def _unpack_legislators(resp):
    return [e['legislator'] for e in resp['legislators']]
def _unpack_districts(resp):
    return [e['district'] for e in resp['districts']]
def _unpack_committees(resp):
    return [e['committee'] for e in resp['committees']]

class Congress(sunlight.service.Service):
    """
    Bindings into the `Sunlight Congress <http://services.sunlightlabs.com/docs/Sunlight_Congress_API/>`_.
    """

    def legislators(self, **kwargs):
        """
        Query the Congress API for all legislators matching certain criteria.

        See documentation at `legislators.get(List)
        <http://services.sunlightlabs.com/docs/congressapi/legislators.get(List)/>`_
        """
        return _unpack_legislators(self.get('legislators.getList', **kwargs))

    def legislators_for_zip(self, zipcode):
        """
        Query the Congress API for all legislators representing a given ZIP
        code.

        This method is not recommended, prefer legislators_for_lat_lon instead.
        See the blog post `"Don't Use Zip Codes Unless You Have To"
        <http://sunlightlabs.com/blog/2012/dont-use-zipcodes/>`_.

        See documentation at `legislators.allForZip
        <http://services.sunlightlabs.com/docs/congressapi/legislators.allForZip/>`_
        """
        return _unpack_legislators(self.get('legislators.allForZip',
                                            zip=zipcode))

    def legislators_for_lat_lon(self, latitude, longitude):
        """
        Query the Congress API for all legislators representing an given
        location.

        See documentation at `legislators.allForLatLong
        <http://services.sunlightlabs.com/docs/congressapi/legislators.allForLatLong/>`_
        """
        return _unpack_legislators(self.get('legislators.allForLatLong',
                                            latitude=latitude,
                                            longitude=longitude))

    def districts_for_zip(self, zipcode):
        """
        Query the Congress API for all congressional districts overlapping a
        zip code.

        See documentation at `districts.getDistrictFromLatLong
        <http://services.sunlightlabs.com/docs/congressapi/districts.getDistrictFromLatLong/>`_
        """
        return _unpack_districts(self.get('districts.getDistrictsFromZip',
                                            zip=zipcode))

    def districts_for_lat_long(self, latitude, longitude):
        """
        Query the Congress API for all congressional districts containing a
        given location.

        See documentation at `districts.getDistrictFromLatLong
        <http://services.sunlightlabs.com/docs/congressapi/districts.getDistrictFromLatLong/>`_
        """
        return _unpack_districts(self.get('districts.getDistrictFromLatLong',
                                            latitude=latitude,
                                            longitude=longitude))

    def committees(self, chamber):
        """
        Query the Congress API for all committees for a chamber.
        (House|Senate|Joint)

        See documentation at `committees.getList
        <http://services.sunlightlabs.com/docs/congressapi/committees.getList/>`_
        """
        return _unpack_committees(self.get('committees.getList',
                                           chamber=chamber))

    def committee_detail(self, id):
        """
        Query the Congress API for all details for a committee, including
        members.

        See documentation at `committees.get
        <http://services.sunlightlabs.com/docs/congressapi/committees.get/>`_
        """
        return self.get('committees.get', id=id)['committee']

    def committees_for_legislator(self, bioguide_id):
        """
        Query the Congress API for all details for a committee, including
        members.

        See documentation at `committees.allForLegislator
        <http://services.sunlightlabs.com/docs/congressapi/committees.allForLegislator/>`_
        """
        return _unpack_committees(self.get('committees.allForLegislator',
                                           bioguide_id=bioguide_id))

    # implementation methods
    def _get_url(self, obj, apikey, **kwargs):
        return "%s/%s?apikey=%s&%s" % (
            service_url,
            obj,
            apikey,
            urllib.urlencode(kwargs.items())
        )

    def _decode_response(self, response):
        return json.loads(response)['response']
<|endoftext|>


<commit_before>#!/usr/bin/env python

"""
postfix_stats.py
~~~~~~~~

:copyright: (c) 2012 DISQUS.
:license: Apache License 2.0, see LICENSE for more details.
"""

import fileinput
import json
import logging
import re
import SocketServer
import sys

from collections import defaultdict, Iterator
from optparse import OptionParser
from Queue import Queue, Full
from threading import Thread, Lock

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger('postfix_stats')

handlers = defaultdict(list)

stats_lock = Lock()

stats = {}
stats['recv'] = {
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['send'] = {
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['in'] = {
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['clients'] = defaultdict(int)
stats['local'] = defaultdict(int)

local_addresses = {}

class Handler(object):
    filter_re = re.compile(r'(?!)')
    facilities = None

    def __init__(self, *args, **kwargs):
        assert isinstance(self.filter_re, re._pattern_type)
        self.facilities = set(self.facilities)

        self.register(self.facilities)

    @classmethod
    def parse(self, line):
        pline = self.filter_re.match(line)

        if pline:
            logger.debug(pline.groupdict())
            self.handle(**pline.groupdict())

    @classmethod
    def handle(self, **kwargs):
        raise NotImplementedError()

    def register(self, facilities):
        facilities = set(facilities)
        for facility in facilities:
            if self not in handlers[facility]:
                handlers[facility].append(self)

        self.facilities |= facilities

class BounceHandler(Handler):
    facilities = set(['bounce'])
    filter_re = re.compile((r'\A(?P<message_id>\w+?): sender non-delivery notification: (?P<bounce_message_id>\w+?)\Z'))

    @classmethod
    def handle(self, message_id=None, bounce_message_id=None):
        pass

class CleanupHandler(Handler):
    facilities = set(['cleanup'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): message-id=\<(?P<ext_message_id>.+?)\>\Z')

    @classmethod
    def handle(self, message_id=None, ext_message_id=None):
        pass

class LocalHandler(Handler):
    facilities = set(['local'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): to=\<(?P<to_email>.*?)\>, orig_to=\<(?P<orig_to_email>.*?)\>, relay=(?P<relay>.+?), delay=(?P<delay>[0-9\.]+), delays=(?P<delays>[0-9\.\/]+), dsn=(?P<dsn>[0-9\.]+), status=(?P<status>\w+) \((?P<response>.+?)\)\Z')
    local_addresses_re = re.compile(r'(?!)')

    def __init__(self, local_addresses_re=None, *args, **kwargs):
        super(LocalHandler, self).__init__(*args, **kwargs)

        if local_addresses_re:
            assert isinstance(local_addresses_re, re._pattern_type)
            self.__class__.local_addresses_re = local_addresses_re

    @classmethod
    def handle(self, message_id=None, to_email=None, orig_to_email=None, relay=None, delay=None, delays=None, dsn=None, status=None, response=None):
        pemail = self.local_addresses_re.search(to_email)

        if pemail:
            search = pemail.group(1)

            name, count = local_addresses[search]

            logger.debug('Local address <%s> count (%s) as "%s"', search, count, name)

            with stats_lock:
                stats['local'][name] += 1
                if count:
                    stats['in']['status'][status] += 1
                    stats['in']['resp_codes'][dsn] += 1

class QmgrHandler(Handler):
    facilities = set(['qmgr'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): (?:(?P<removed>removed)|(?:from=\<(?P<from_address>.*?)\>, size=(?P<size>[0-9]+), nrcpt=(?P<nrcpt>[0-9]+) \(queue (?P<queue>[a-z]+)\)))?\Z')

    @classmethod
    def handle(self, message_id=None, removed=None, from_address=None, size=None, nrcpt=None, queue=None):
        pass

class SmtpHandler(Handler):
    facilities = set(['smtp', 'error'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): to=\<(?P<to_email>.+?)\>, relay=(?P<relay>.+?), (?:conn_use=(?P<conn_use>\d), )?delay=(?P<delay>[0-9\.]+), delays=(?P<delays>[0-9\.\/]+), dsn=(?P<dsn>[0-9\.]+), status=(?P<status>\w+) \((?P<response>.+?)\)\Z')

    @classmethod
    def handle(self, message_id=None, to_email=None, relay=None, conn_use=None, delay=None, delays=None, dsn=None, status=None, response=None):
        with stats_lock:
            stat = 'recv' if '127.0.0.1' in relay else 'send'
            stats[stat]['status'][status] += 1
            stats[stat]['resp_codes'][dsn] += 1

class SmtpdHandler(Handler):
    facilities = set(['smtpd'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): client=(?P<client_hostname>[.\w-]+)\[(?P<client_ip>[A-Fa-f0-9.:]{3,39})\](?:, sasl_method=[\w-]+)?(?:, sasl_username=[-_.@\w]+)?(?:, sasl_sender=\S)?(?:, orig_queue_id=\w+)?(?:, orig_client=(?P<orig_client_hostname>[.\w-]+)\[(?P<orig_client_ip>[A-Fa-f0-9.:]{3,39})\])?\Z')

    @classmethod
    def handle(self, message_id=None, client_hostname=None, client_ip=None, orig_client_hostname=None, orig_client_ip=None):
        ip = orig_client_ip or client_ip
        with stats_lock:
            stats['clients'][ip] += 1

class Parser(Thread):
    line_re = re.compile(r'\A(?P<iso_date>\D{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+(?P<source>.+?)\s+(?P<facility>.+?)\[(?P<pid>\d+?)\]:\s(?P<message>.*)\Z')

    def __init__(self, lines):
        super(Parser, self).__init__()
        self.lines = lines

        self.daemon = True
        self.start()

    def run(self):
        while True:
            line = self.lines.get()

            try:
                self.parse_line(line)
            except Exception, e:
                logger.exception('Error parsing line: %s', line)
            finally:
                self.lines.task_done()

    def parse_line(self, line):
        pln = self.line_re.match(line)

        if pln:
            pline = pln.groupdict()
            logger.debug(pline)

            facility = pline['facility'].split('/')

            for handler in handlers[facility[-1]]:
                handler.parse(pline['message'])

class ParserPool(object):
    def __init__(self, num_parsers):
        self.lines = Queue(num_parsers * 1000)

        for i in xrange(num_parsers):
            logger.info('Starting parser %s', i)
            Parser(self.lines)

    def add_line(self, line, block=False):
        self.lines.put(line, block)

    def join(self):
        self.lines.join()

class CommandHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        command = self.rfile.readline().strip()

        logger.info('Got command: %s', command)

        if command.lower() == 'stats':
            self.wfile.write(json.dumps(stats))
        elif command.lower() == 'prettystats':
            self.wfile.write(json.dumps(stats, indent=2))

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

class StdinReader(Iterator):
    def next(self):
        try:
            line = sys.stdin.readline()
        except KeyboardInterrupt:
            raise StopIteration

        if not line:
            raise StopIteration

        return line

    def isstdin(self):
        return True

def main(logs, daemon=False, host='127.0.0.1', port=7777, concurrency=2, local_emails=None, **kwargs):
    true_values = ['yes', '1', 'true']
    for local_email in local_emails:
        try:
            search, name, count = local_email.strip('()').split(',')
        except ValueError:
            logger.error('LOCAL_TUPLE requires 3 fields: %s', local_email)
            return -1

        local_addresses[search] = (name, count.lower() in true_values)

    if local_addresses:
        local_addresses_re = re.compile(r'(%s)' % '|'.join(local_addresses.keys()))
        logger.debug('Local email pattern: %s', local_addresses_re.pattern)
    else:
        local_addresses_re = re.compile(r'(?!)')

    handlers = (LocalHandler(local_addresses_re), SmtpHandler(), SmtpdHandler())

    if daemon:
        server = ThreadedTCPServer((host, port), CommandHandler)
        server_thread = Thread(target=server.serve_forever)
        server_thread.daemon = True
        server_thread.start()
        logger.info('Listening on %s:%s', host, port)

    parser_pool = ParserPool(concurrency)

    if not logs or logs[0] is '-':
        reader = StdinReader()
    else:
        reader = fileinput.input(logs)

    for line in reader:
        try:
            parser_pool.add_line(line.strip('\n'), not reader.isstdin())
        except Full:
            logger.warning('Line parser queue full')
            # Dont really care
            pass

    parser_pool.join()

    if not daemon:
        print json.dumps(stats, indent=2)
    else:
        server.shutdown()

    return 0

if __name__ == '__main__':
    usage = "usage: %prog [options] file1 file2 ... fileN"
    opt_parser = OptionParser(usage)
    opt_parser.add_option("-v", "--verbose", dest="verbosity", default=0, action="count",
        help="-v for a little info, -vv for debugging")
    opt_parser.add_option("-d", "--daemon", dest="daemon", default=False, action="store_true",
        help="Run tcp server for getting stats from")
    opt_parser.add_option("-p", "--port", dest="port", default=7777, type="int",
        help="Port to listen on for grabbing stats", metavar="PORT")
    opt_parser.add_option("-i", "--host", dest="host", default="127.0.0.1",
        help="Host/IP to listen on for grabbing stats", metavar="HOST")
    opt_parser.add_option("-c", "--concurrency", dest="concurrency", default=2, type="int",
        help="Number of threads to spawn for handling lines", metavar="NUM")
    opt_parser.add_option("-l", "--local", dest="local_emails", default=[], action="append",
        help="Search for STRING in incoming email addresses and incr stat NAME and if COUNT, count in incoming - STRING,NAME,COUNT", metavar="LOCAL_TUPLE")

    (options, args) = opt_parser.parse_args()

    if options.verbosity == 1:
        logger.setLevel(logging.INFO)
    elif options.verbosity == 2:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.ERROR)

    sys.exit(main(args, **options.__dict__))
<commit_msg>Allow colelcitng postfix stats for relay-mode<commit_after>#!/usr/bin/env python

"""
postfix_stats.py
~~~~~~~~

:copyright: (c) 2012 DISQUS.
:license: Apache License 2.0, see LICENSE for more details.
"""

import fileinput
import json
import logging
import re
import SocketServer
import sys

from collections import defaultdict, Iterator
from optparse import OptionParser
from Queue import Queue, Full
from threading import Thread, Lock

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger('postfix_stats')

handlers = defaultdict(list)

stats_lock = Lock()

stats = {}
stats['recv'] = {
    'relay_status': defaultdict(lambda: defaultdict(int)),
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['send'] = {
    'relay_status': defaultdict(lambda: defaultdict(int)),
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['in'] = {
    'status': defaultdict(int),
    'resp_codes': defaultdict(int),
}
stats['relay_clients'] = defaultdict(lambda: defaultdict(int))
stats['clients'] = defaultdict(int)
stats['local'] = defaultdict(int)

local_addresses = {}

class Handler(object):
    filter_re = re.compile(r'(?!)')
    facilities = None
    component = None

    def __init__(self, *args, **kwargs):
        assert isinstance(self.filter_re, re._pattern_type)
        self.facilities = set(self.facilities)

        self.register(self.facilities)

    @classmethod
    def parse(self, line):
        pline = self.filter_re.match(line)

        if pline:
            logger.debug(pline.groupdict())
            self.handle(**pline.groupdict())

    @classmethod
    def handle(self, **kwargs):
        raise NotImplementedError()

    def register(self, facilities):
        facilities = set(facilities)
        for facility in facilities:
            if self not in handlers[facility]:
                handlers[facility].append(self)

        self.facilities |= facilities

class BounceHandler(Handler):
    facilities = set(['bounce'])
    filter_re = re.compile((r'\A(?P<message_id>\w+?): sender non-delivery notification: (?P<bounce_message_id>\w+?)\Z'))

    @classmethod
    def handle(self, message_id=None, bounce_message_id=None):
        pass

class CleanupHandler(Handler):
    facilities = set(['cleanup'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): message-id=\<(?P<ext_message_id>.+?)\>\Z')

    @classmethod
    def handle(self, message_id=None, ext_message_id=None):
        pass

class LocalHandler(Handler):
    facilities = set(['local'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): to=\<(?P<to_email>.*?)\>, orig_to=\<(?P<orig_to_email>.*?)\>, relay=(?P<relay>.+?), delay=(?P<delay>[0-9\.]+), delays=(?P<delays>[0-9\.\/]+), dsn=(?P<dsn>[0-9\.]+), status=(?P<status>\w+) \((?P<response>.+?)\)\Z')
    local_addresses_re = re.compile(r'(?!)')

    def __init__(self, local_addresses_re=None, *args, **kwargs):
        super(LocalHandler, self).__init__(*args, **kwargs)

        if local_addresses_re:
            assert isinstance(local_addresses_re, re._pattern_type)
            self.__class__.local_addresses_re = local_addresses_re

    @classmethod
    def handle(self, message_id=None, to_email=None, orig_to_email=None, relay=None, delay=None, delays=None, dsn=None, status=None, response=None):
        pemail = self.local_addresses_re.search(to_email)

        if pemail:
            search = pemail.group(1)

            name, count = local_addresses[search]

            logger.debug('Local address <%s> count (%s) as "%s"', search, count, name)

            with stats_lock:
                stats['local'][name] += 1
                if count:
                    stats['in']['status'][status] += 1
                    stats['in']['resp_codes'][dsn] += 1

class QmgrHandler(Handler):
    facilities = set(['qmgr'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): (?:(?P<removed>removed)|(?:from=\<(?P<from_address>.*?)\>, size=(?P<size>[0-9]+), nrcpt=(?P<nrcpt>[0-9]+) \(queue (?P<queue>[a-z]+)\)))?\Z')

    @classmethod
    def handle(self, message_id=None, removed=None, from_address=None, size=None, nrcpt=None, queue=None):
        pass

class SmtpHandler(Handler):
    facilities = set(['smtp', 'error'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): to=\<(?P<to_email>.+?)\>, relay=(?P<relay>.+?), (?:conn_use=(?P<conn_use>\d), )?delay=(?P<delay>[0-9\.]+), delays=(?P<delays>[0-9\.\/]+), dsn=(?P<dsn>[0-9\.]+), status=(?P<status>\w+) \((?P<response>.+?)\)\Z')

    @classmethod
    def handle(self, message_id=None, to_email=None, relay=None, conn_use=None, delay=None, delays=None, dsn=None, status=None, response=None):
        with stats_lock:
            stat = 'recv' if '127.0.0.1' in relay else 'send'
            if self.component is None:
                stats[stat]['status'][status] += 1
            else:
                stats[stat]['relay_status'][self.component][status] += 1
            stats[stat]['resp_codes'][dsn] += 1

class SmtpdHandler(Handler):
    facilities = set(['smtpd'])
    filter_re = re.compile(r'\A(?P<message_id>\w+?): client=(?P<client_hostname>[.\w-]+)\[(?P<client_ip>[A-Fa-f0-9.:]{3,39})\](?:, sasl_method=[\w-]+)?(?:, sasl_username=[-_.@\w]+)?(?:, sasl_sender=\S)?(?:, orig_queue_id=\w+)?(?:, orig_client=(?P<orig_client_hostname>[.\w-]+)\[(?P<orig_client_ip>[A-Fa-f0-9.:]{3,39})\])?\Z')

    @classmethod
    def handle(self, message_id=None, client_hostname=None, client_ip=None, orig_client_hostname=None, orig_client_ip=None):
        ip = orig_client_ip or client_ip
        with stats_lock:

            if self.component is None:
                stats['clients'][ip] += 1
            else:
                stats['relay_clients'][self.component][ip] += 1

class Parser(Thread):
    line_re = re.compile(r'\A(?P<iso_date>\D{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+(?P<source>.+?)\s+(?P<facility>.+?)\[(?P<pid>\d+?)\]:\s(?P<message>.*)\Z')

    def __init__(self, lines):
        super(Parser, self).__init__()
        self.lines = lines

        self.daemon = True
        self.start()

    def run(self):
        while True:
            line = self.lines.get()

            try:
                self.parse_line(line)
            except Exception, e:
                logger.exception('Error parsing line: %s', line)
            finally:
                self.lines.task_done()

    def parse_line(self, line):
        pln = self.line_re.match(line)

        if pln:
            pline = pln.groupdict()
            logger.debug(pline)

            component, facility = pline['facility'].split('/')
            component = component.replace('postfix-', '')

            for handler in handlers[facility]:
                Handler.component = component
                handler.parse(pline['message'])

class ParserPool(object):
    def __init__(self, num_parsers):
        self.lines = Queue(num_parsers * 1000)

        for i in xrange(num_parsers):
            logger.info('Starting parser %s', i)
            Parser(self.lines)

    def add_line(self, line, block=False):
        self.lines.put(line, block)

    def join(self):
        self.lines.join()

class CommandHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        command = self.rfile.readline().strip()

        logger.info('Got command: %s', command)

        if command.lower() == 'stats':
            self.wfile.write(json.dumps(stats))
        elif command.lower() == 'prettystats':
            self.wfile.write(json.dumps(stats, indent=2))

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

class StdinReader(Iterator):
    def next(self):
        try:
            line = sys.stdin.readline()
        except KeyboardInterrupt:
            raise StopIteration

        if not line:
            raise StopIteration

        return line

    def isstdin(self):
        return True

def main(logs, daemon=False, host='127.0.0.1', port=7777, concurrency=2, local_emails=None, **kwargs):
    true_values = ['yes', '1', 'true']
    for local_email in local_emails:
        try:
            search, name, count = local_email.strip('()').split(',')
        except ValueError:
            logger.error('LOCAL_TUPLE requires 3 fields: %s', local_email)
            return -1

        local_addresses[search] = (name, count.lower() in true_values)

    if local_addresses:
        local_addresses_re = re.compile(r'(%s)' % '|'.join(local_addresses.keys()))
        logger.debug('Local email pattern: %s', local_addresses_re.pattern)
    else:
        local_addresses_re = re.compile(r'(?!)')

    handlers = (LocalHandler(local_addresses_re), SmtpHandler(), SmtpdHandler())

    if daemon:
        server = ThreadedTCPServer((host, port), CommandHandler)
        server_thread = Thread(target=server.serve_forever)
        server_thread.daemon = True
        server_thread.start()
        logger.info('Listening on %s:%s', host, port)

    parser_pool = ParserPool(concurrency)

    if not logs or logs[0] is '-':
        reader = StdinReader()
    else:
        reader = fileinput.input(logs)

    for line in reader:
        try:
            parser_pool.add_line(line.strip('\n'), not reader.isstdin())
        except Full:
            logger.warning('Line parser queue full')
            # Dont really care
            pass

    parser_pool.join()

    if not daemon:
        print json.dumps(stats, indent=2)
    else:
        server.shutdown()

    return 0

if __name__ == '__main__':
    usage = "usage: %prog [options] file1 file2 ... fileN"
    opt_parser = OptionParser(usage)
    opt_parser.add_option("-v", "--verbose", dest="verbosity", default=0, action="count",
        help="-v for a little info, -vv for debugging")
    opt_parser.add_option("-d", "--daemon", dest="daemon", default=False, action="store_true",
        help="Run tcp server for getting stats from")
    opt_parser.add_option("-p", "--port", dest="port", default=7777, type="int",
        help="Port to listen on for grabbing stats", metavar="PORT")
    opt_parser.add_option("-i", "--host", dest="host", default="127.0.0.1",
        help="Host/IP to listen on for grabbing stats", metavar="HOST")
    opt_parser.add_option("-c", "--concurrency", dest="concurrency", default=2, type="int",
        help="Number of threads to spawn for handling lines", metavar="NUM")
    opt_parser.add_option("-l", "--local", dest="local_emails", default=[], action="append",
        help="Search for STRING in incoming email addresses and incr stat NAME and if COUNT, count in incoming - STRING,NAME,COUNT", metavar="LOCAL_TUPLE")

    (options, args) = opt_parser.parse_args()

    if options.verbosity == 1:
        logger.setLevel(logging.INFO)
    elif options.verbosity == 2:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.ERROR)

    sys.exit(main(args, **options.__dict__))
<|endoftext|>


<commit_before>from __future__ import unicode_literals
import urlparse

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
try:
    from django.contrib.auth import get_user_model  # Django 1.5
except ImportError:
    from postman.future_1_5 import get_user_model
from django.contrib.sites.models import get_current_site
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
try:
    from django.utils.timezone import now  # Django 1.4 aware datetimes
except ImportError:
    from datetime import datetime
    now = datetime.now
from django.utils.translation import ugettext as _, ugettext_lazy as lz_
from django.views.decorators.csrf import csrf_protect
from django.views.generic import FormView, TemplateView, View

from . import OPTION_MESSAGES
from .fields import autocompleter_app
from .forms import WriteForm, AnonymousWriteForm, QuickReplyForm, FullReplyForm
from .models import Message, get_order_by
from .utils import format_subject, format_body

login_required_m = method_decorator(login_required)
csrf_protect_m = method_decorator(csrf_protect)

##########
# Helpers
##########
def _get_referer(request):
    """Return the HTTP_REFERER, if existing."""
    if 'HTTP_REFERER' in request.META:
        sr = urlparse.urlsplit(request.META['HTTP_REFERER'])
        return urlparse.urlunsplit(('', '', sr.path, sr.query, sr.fragment))

########
# Views
########
class FolderMixin(object):
    """Code common to the folders."""
    http_method_names = ['get']

    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(FolderMixin, self).dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(FolderMixin, self).get_context_data(**kwargs)
        params = {}
        option = kwargs.get('option')
        if option:
            params['option'] = option
        order_by = get_order_by(self.request.GET)
        if order_by:
            params['order_by'] = order_by
        msgs = getattr(Message.objects, self.folder_name)(self.request.user, **params)
        context.update({
            'pm_messages': msgs,  # avoid 'messages', already used by contrib.messages
            'by_conversation': option is None,
            'by_message': option == OPTION_MESSAGES,
            'by_conversation_url': reverse(self.view_name),
            'by_message_url': reverse(self.view_name, args=[OPTION_MESSAGES]),
            'current_url': self.request.get_full_path(),
            'gets': self.request.GET,  # useful to postman_order_by template tag
        })
        return context

class InboxView(FolderMixin, TemplateView):
    """
    Display the list of received messages for the current user.

    Optional URLconf name-based argument:
        ``option``: display option:
            OPTION_MESSAGES to view all messages
            default to None to view only the last message for each conversation
    Optional URLconf configuration attribute:
        ``template_name``: the name of the template to use

    """
    # for FolderMixin:
    folder_name = 'inbox'
    view_name = 'postman_inbox'
    # for TemplateView:
    template_name = 'postman/inbox.html'

class SentView(FolderMixin, TemplateView):
    """
    Display the list of sent messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'sent'
    view_name = 'postman_sent'
    # for TemplateView:
    template_name = 'postman/sent.html'

class ArchivesView(FolderMixin, TemplateView):
    """
    Display the list of archived messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'archives'
    view_name = 'postman_archives'
    # for TemplateView:
    template_name = 'postman/archives.html'

class TrashView(FolderMixin, TemplateView):
    """
    Display the list of deleted messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'trash'
    view_name = 'postman_trash'
    # for TemplateView:
    template_name = 'postman/trash.html'

class ComposeMixin(object):
    """
    Code common to the write and reply views.

    Optional attributes:
        ``success_url``: where to redirect to after a successful POST
        ``user_filter``: a filter for recipients
        ``exchange_filter``: a filter for exchanges between a sender and a recipient
        ``max``: an upper limit for the recipients number
        ``auto_moderators``: a list of auto-moderation functions

    """
    http_method_names = ['get', 'post']
    success_url = None
    user_filter = None
    exchange_filter = None
    max = None
    auto_moderators = []

    def get_form_kwargs(self):
        kwargs = super(ComposeMixin, self).get_form_kwargs()
        if self.request.method == 'POST':
            kwargs.update({
                'sender': self.request.user,
                'user_filter': self.user_filter,
                'exchange_filter': self.exchange_filter,
                'max': self.max,
                'site': get_current_site(self.request),
            })
        return kwargs

    def get_success_url(self):
        return self.request.GET.get('next') or self.success_url or _get_referer(self.request) or 'postman_inbox'

    def form_valid(self, form):
        params = {'auto_moderators': self.auto_moderators}
        if hasattr(self, 'parent'):  # only in the ReplyView case
            params['parent'] = self.parent
        is_successful = form.save(**params)
        if is_successful:
            messages.success(self.request, _("Message successfully sent."), fail_silently=True)
        else:
            messages.warning(self.request, _("Message rejected for at least one recipient."), fail_silently=True)
        return redirect(self.get_success_url())

    def get_context_data(self, **kwargs):
        context = super(ComposeMixin, self).get_context_data(**kwargs)
        context.update({
            'autocompleter_app': autocompleter_app,
            'next_url': self.request.GET.get('next') or _get_referer(self.request),
        })
        return context

class WriteView(ComposeMixin, FormView):
    """
    Display a form to compose a message.

    Optional URLconf name-based argument:
        ``recipients``: a colon-separated list of usernames
    Optional attributes:
        ``form_classes``: a 2-tuple of form classes
        ``autocomplete_channels``: a channel name or a 2-tuple of names
        ``template_name``: the name of the template to use
        + those of ComposeMixin

    """
    form_classes = (WriteForm, AnonymousWriteForm)
    autocomplete_channels = None
    template_name = 'postman/write.html'

    @csrf_protect_m
    def dispatch(self, *args, **kwargs):
        if getattr(settings, 'POSTMAN_DISALLOW_ANONYMOUS', False):
            return login_required(super(WriteView, self).dispatch)(*args, **kwargs)
        return super(WriteView, self).dispatch(*args, **kwargs)

    def get_form_class(self):
        return self.form_classes[0] if self.request.user.is_authenticated() else self.form_classes[1]

    def get_initial(self):
        initial = super(WriteView, self).get_initial()
        if self.request.method == 'GET':
            initial.update(self.request.GET.items())  # allow optional initializations by query string
            recipients = self.kwargs.get('recipients')
            if recipients:
                # order_by() is not mandatory, but: a) it doesn't hurt; b) it eases the test suite
                # and anyway the original ordering cannot be respected.
                user_model = get_user_model()
                usernames = list(user_model.objects.values_list(user_model.USERNAME_FIELD, flat=True).filter(
                    is_active=True,
                    **{'{0}__in'.format(user_model.USERNAME_FIELD): [r.strip() for r in recipients.split(':') if r and not r.isspace()]}
                ).order_by(user_model.USERNAME_FIELD))
                if usernames:
                    initial['recipients'] = ', '.join(usernames)
        return initial

    def get_form_kwargs(self):
        kwargs = super(WriteView, self).get_form_kwargs()
        if isinstance(self.autocomplete_channels, tuple) and len(self.autocomplete_channels) == 2:
            channel = self.autocomplete_channels[self.request.user.is_anonymous()]
        else:
            channel = self.autocomplete_channels
        kwargs['channel'] = channel
        return kwargs

class ReplyView(ComposeMixin, FormView):
    """
    Display a form to compose a reply.

    Optional attributes:
        ``form_class``: the form class to use
        ``formatters``: a 2-tuple of functions to prefill the subject and body fields
        ``autocomplete_channel``: a channel name
        ``template_name``: the name of the template to use
        + those of ComposeMixin

    """
    form_class = FullReplyForm
    formatters = (format_subject, format_body)
    autocomplete_channel = None
    template_name = 'postman/reply.html'

    @csrf_protect_m
    @login_required_m
    def dispatch(self, request, message_id, *args, **kwargs):
        perms = Message.objects.perms(request.user)
        self.parent = get_object_or_404(Message, perms, pk=message_id)
        return super(ReplyView, self).dispatch(request,*args, **kwargs)

    def get_initial(self):
        self.initial = self.parent.quote(*self.formatters)  # will also be partially used in get_form_kwargs()
        if self.request.method == 'GET':
            self.initial.update(self.request.GET.items())  # allow overwriting of the defaults by query string
        return self.initial

    def get_form_kwargs(self):
        kwargs = super(ReplyView, self).get_form_kwargs()
        kwargs['channel'] = self.autocomplete_channel
        if self.request.method == 'POST':
            if 'subject' not in kwargs['data']:  # case of the quick reply form
                post = kwargs['data'].copy()  # self.request.POST is immutable
                post['subject'] = self.initial['subject']
                kwargs['data'] = post
            kwargs['recipient'] = self.parent.sender or self.parent.email
        return kwargs

    def get_context_data(self, **kwargs):
        context = super(ReplyView, self).get_context_data(**kwargs)
        context['recipient'] = self.parent.obfuscated_sender
        return context

class DisplayMixin(object):
    """
    Code common to the by-message and by-conversation views.

    Optional attributes:
        ``form_class``: the form class to use
        ``formatters``: a 2-tuple of functions to prefill the subject and body fields
        ``template_name``: the name of the template to use

    """
    http_method_names = ['get']
    form_class = QuickReplyForm
    formatters = (format_subject, format_body)
    template_name = 'postman/view.html'

    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(DisplayMixin, self).dispatch(*args, **kwargs)

    def get(self, request, *args, **kwargs):
        user = request.user
        self.msgs = Message.objects.thread(user, self.filter)
        if not self.msgs:
            raise Http404
        Message.objects.set_read(user, self.filter)
        return super(DisplayMixin, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(DisplayMixin, self).get_context_data(**kwargs)
        user = self.request.user
        # are all messages archived ?
        for m in self.msgs:
            if not getattr(m, ('sender' if m.sender == user else 'recipient') + '_archived'):
                archived = False
                break
        else:
            archived = True
        # look for the most recent received message (and non-deleted to comply with the future perms() control), if any
        for m in reversed(self.msgs):
            if m.recipient == user and not m.recipient_deleted_at:
                received = m
                break
        else:
            received = None
        context.update({
            'pm_messages': self.msgs,
            'archived': archived,
            'reply_to_pk': received.pk if received else None,
            'form': self.form_class(initial=received.quote(*self.formatters)) if received else None,
            'next_url': self.request.GET.get('next') or reverse('postman_inbox'),
        })
        return context

class MessageView(DisplayMixin, TemplateView):
    """Display one specific message."""

    def get(self, request, message_id, *args, **kwargs):
        self.filter = Q(pk=message_id)
        return super(MessageView, self).get(request, *args, **kwargs)

class ConversationView(DisplayMixin, TemplateView):
    """Display a conversation."""

    def get(self, request, thread_id, *args, **kwargs):
        self.filter = Q(thread=thread_id)
        return super(ConversationView, self).get(request, *args, **kwargs)

class UpdateMessageMixin(object):
    """
    Code common to the archive/delete/undelete actions.

    Attributes:
        ``field_bit``: a part of the name of the field to update
        ``success_msg``: the displayed text in case of success
    Optional attributes:
        ``field_value``: the value to set in the field
        ``success_url``: where to redirect to after a successful POST

    """
    http_method_names = ['post']
    field_value = None
    success_url = None

    @csrf_protect_m
    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(UpdateMessageMixin, self).dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        next_url = _get_referer(request) or 'postman_inbox'
        pks = request.POST.getlist('pks')
        tpks = request.POST.getlist('tpks')
        if pks or tpks:
            user = request.user
            filter = Q(pk__in=pks) | Q(thread__in=tpks)
            recipient_rows = Message.objects.as_recipient(user, filter).update(**{'recipient_{0}'.format(self.field_bit): self.field_value})
            sender_rows = Message.objects.as_sender(user, filter).update(**{'sender_{0}'.format(self.field_bit): self.field_value})
            if not (recipient_rows or sender_rows):
                raise Http404  # abnormal enough, like forged ids
            messages.success(request, self.success_msg, fail_silently=True)
            return redirect(request.GET.get('next') or self.success_url or next_url)
        else:
            messages.warning(request, _("Select at least one object."), fail_silently=True)
            return redirect(next_url)

class ArchiveView(UpdateMessageMixin, View):
    """Mark messages/conversations as archived."""
    field_bit = 'archived'
    success_msg = lz_("Messages or conversations successfully archived.")
    field_value = True

class DeleteView(UpdateMessageMixin, View):
    """Mark messages/conversations as deleted."""
    field_bit = 'deleted_at'
    success_msg = lz_("Messages or conversations successfully deleted.")
    field_value = now()

class UndeleteView(UpdateMessageMixin, View):
    """Revert messages/conversations from marked as deleted."""
    field_bit = 'deleted_at'
    success_msg = lz_("Messages or conversations successfully recovered.")
<commit_msg>Added set_read method to the DisplayMessageMixin<commit_after>from __future__ import unicode_literals
import urlparse

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
try:
    from django.contrib.auth import get_user_model  # Django 1.5
except ImportError:
    from postman.future_1_5 import get_user_model
from django.contrib.sites.models import get_current_site
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
try:
    from django.utils.timezone import now  # Django 1.4 aware datetimes
except ImportError:
    from datetime import datetime
    now = datetime.now
from django.utils.translation import ugettext as _, ugettext_lazy as lz_
from django.views.decorators.csrf import csrf_protect
from django.views.generic import FormView, TemplateView, View

from . import OPTION_MESSAGES
from .fields import autocompleter_app
from .forms import WriteForm, AnonymousWriteForm, QuickReplyForm, FullReplyForm
from .models import Message, get_order_by
from .utils import format_subject, format_body

login_required_m = method_decorator(login_required)
csrf_protect_m = method_decorator(csrf_protect)

##########
# Helpers
##########
def _get_referer(request):
    """Return the HTTP_REFERER, if existing."""
    if 'HTTP_REFERER' in request.META:
        sr = urlparse.urlsplit(request.META['HTTP_REFERER'])
        return urlparse.urlunsplit(('', '', sr.path, sr.query, sr.fragment))

########
# Views
########
class FolderMixin(object):
    """Code common to the folders."""
    http_method_names = ['get']

    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(FolderMixin, self).dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(FolderMixin, self).get_context_data(**kwargs)
        params = {}
        option = kwargs.get('option')
        if option:
            params['option'] = option
        order_by = get_order_by(self.request.GET)
        if order_by:
            params['order_by'] = order_by
        msgs = getattr(Message.objects, self.folder_name)(self.request.user, **params)
        context.update({
            'pm_messages': msgs,  # avoid 'messages', already used by contrib.messages
            'by_conversation': option is None,
            'by_message': option == OPTION_MESSAGES,
            'by_conversation_url': reverse(self.view_name),
            'by_message_url': reverse(self.view_name, args=[OPTION_MESSAGES]),
            'current_url': self.request.get_full_path(),
            'gets': self.request.GET,  # useful to postman_order_by template tag
        })
        return context

class InboxView(FolderMixin, TemplateView):
    """
    Display the list of received messages for the current user.

    Optional URLconf name-based argument:
        ``option``: display option:
            OPTION_MESSAGES to view all messages
            default to None to view only the last message for each conversation
    Optional URLconf configuration attribute:
        ``template_name``: the name of the template to use

    """
    # for FolderMixin:
    folder_name = 'inbox'
    view_name = 'postman_inbox'
    # for TemplateView:
    template_name = 'postman/inbox.html'

class SentView(FolderMixin, TemplateView):
    """
    Display the list of sent messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'sent'
    view_name = 'postman_sent'
    # for TemplateView:
    template_name = 'postman/sent.html'

class ArchivesView(FolderMixin, TemplateView):
    """
    Display the list of archived messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'archives'
    view_name = 'postman_archives'
    # for TemplateView:
    template_name = 'postman/archives.html'

class TrashView(FolderMixin, TemplateView):
    """
    Display the list of deleted messages for the current user.

    Optional arguments and attributes: refer to InboxView.

    """
    # for FolderMixin:
    folder_name = 'trash'
    view_name = 'postman_trash'
    # for TemplateView:
    template_name = 'postman/trash.html'

class ComposeMixin(object):
    """
    Code common to the write and reply views.

    Optional attributes:
        ``success_url``: where to redirect to after a successful POST
        ``user_filter``: a filter for recipients
        ``exchange_filter``: a filter for exchanges between a sender and a recipient
        ``max``: an upper limit for the recipients number
        ``auto_moderators``: a list of auto-moderation functions

    """
    http_method_names = ['get', 'post']
    success_url = None
    user_filter = None
    exchange_filter = None
    max = None
    auto_moderators = []

    def get_form_kwargs(self):
        kwargs = super(ComposeMixin, self).get_form_kwargs()
        if self.request.method == 'POST':
            kwargs.update({
                'sender': self.request.user,
                'user_filter': self.user_filter,
                'exchange_filter': self.exchange_filter,
                'max': self.max,
                'site': get_current_site(self.request),
            })
        return kwargs

    def get_success_url(self):
        return self.request.GET.get('next') or self.success_url or _get_referer(self.request) or 'postman_inbox'

    def form_valid(self, form):
        params = {'auto_moderators': self.auto_moderators}
        if hasattr(self, 'parent'):  # only in the ReplyView case
            params['parent'] = self.parent
        is_successful = form.save(**params)
        if is_successful:
            messages.success(self.request, _("Message successfully sent."), fail_silently=True)
        else:
            messages.warning(self.request, _("Message rejected for at least one recipient."), fail_silently=True)
        return redirect(self.get_success_url())

    def get_context_data(self, **kwargs):
        context = super(ComposeMixin, self).get_context_data(**kwargs)
        context.update({
            'autocompleter_app': autocompleter_app,
            'next_url': self.request.GET.get('next') or _get_referer(self.request),
        })
        return context

class WriteView(ComposeMixin, FormView):
    """
    Display a form to compose a message.

    Optional URLconf name-based argument:
        ``recipients``: a colon-separated list of usernames
    Optional attributes:
        ``form_classes``: a 2-tuple of form classes
        ``autocomplete_channels``: a channel name or a 2-tuple of names
        ``template_name``: the name of the template to use
        + those of ComposeMixin

    """
    form_classes = (WriteForm, AnonymousWriteForm)
    autocomplete_channels = None
    template_name = 'postman/write.html'

    @csrf_protect_m
    def dispatch(self, *args, **kwargs):
        if getattr(settings, 'POSTMAN_DISALLOW_ANONYMOUS', False):
            return login_required(super(WriteView, self).dispatch)(*args, **kwargs)
        return super(WriteView, self).dispatch(*args, **kwargs)

    def get_form_class(self):
        return self.form_classes[0] if self.request.user.is_authenticated() else self.form_classes[1]

    def get_initial(self):
        initial = super(WriteView, self).get_initial()
        if self.request.method == 'GET':
            initial.update(self.request.GET.items())  # allow optional initializations by query string
            recipients = self.kwargs.get('recipients')
            if recipients:
                # order_by() is not mandatory, but: a) it doesn't hurt; b) it eases the test suite
                # and anyway the original ordering cannot be respected.
                user_model = get_user_model()
                usernames = list(user_model.objects.values_list(user_model.USERNAME_FIELD, flat=True).filter(
                    is_active=True,
                    **{'{0}__in'.format(user_model.USERNAME_FIELD): [r.strip() for r in recipients.split(':') if r and not r.isspace()]}
                ).order_by(user_model.USERNAME_FIELD))
                if usernames:
                    initial['recipients'] = ', '.join(usernames)
        return initial

    def get_form_kwargs(self):
        kwargs = super(WriteView, self).get_form_kwargs()
        if isinstance(self.autocomplete_channels, tuple) and len(self.autocomplete_channels) == 2:
            channel = self.autocomplete_channels[self.request.user.is_anonymous()]
        else:
            channel = self.autocomplete_channels
        kwargs['channel'] = channel
        return kwargs

class ReplyView(ComposeMixin, FormView):
    """
    Display a form to compose a reply.

    Optional attributes:
        ``form_class``: the form class to use
        ``formatters``: a 2-tuple of functions to prefill the subject and body fields
        ``autocomplete_channel``: a channel name
        ``template_name``: the name of the template to use
        + those of ComposeMixin

    """
    form_class = FullReplyForm
    formatters = (format_subject, format_body)
    autocomplete_channel = None
    template_name = 'postman/reply.html'

    @csrf_protect_m
    @login_required_m
    def dispatch(self, request, message_id, *args, **kwargs):
        perms = Message.objects.perms(request.user)
        self.parent = get_object_or_404(Message, perms, pk=message_id)
        return super(ReplyView, self).dispatch(request,*args, **kwargs)

    def get_initial(self):
        self.initial = self.parent.quote(*self.formatters)  # will also be partially used in get_form_kwargs()
        if self.request.method == 'GET':
            self.initial.update(self.request.GET.items())  # allow overwriting of the defaults by query string
        return self.initial

    def get_form_kwargs(self):
        kwargs = super(ReplyView, self).get_form_kwargs()
        kwargs['channel'] = self.autocomplete_channel
        if self.request.method == 'POST':
            if 'subject' not in kwargs['data']:  # case of the quick reply form
                post = kwargs['data'].copy()  # self.request.POST is immutable
                post['subject'] = self.initial['subject']
                kwargs['data'] = post
            kwargs['recipient'] = self.parent.sender or self.parent.email
        return kwargs

    def get_context_data(self, **kwargs):
        context = super(ReplyView, self).get_context_data(**kwargs)
        context['recipient'] = self.parent.obfuscated_sender
        return context

class DisplayMixin(object):
    """
    Code common to the by-message and by-conversation views.

    Optional attributes:
        ``form_class``: the form class to use
        ``formatters``: a 2-tuple of functions to prefill the subject and body fields
        ``template_name``: the name of the template to use

    """
    http_method_names = ['get']
    form_class = QuickReplyForm
    formatters = (format_subject, format_body)
    template_name = 'postman/view.html'

    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(DisplayMixin, self).dispatch(*args, **kwargs)

    def set_read(self, user):
        """ Override this method for the caching and other purposes
        """
        Message.objects.set_read(user, self.filter)

    def get(self, request, *args, **kwargs):
        user = request.user
        self.msgs = Message.objects.thread(user, self.filter)
        if not self.msgs:
            raise Http404
        self.set_read(user)
        return super(DisplayMixin, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(DisplayMixin, self).get_context_data(**kwargs)
        user = self.request.user
        # are all messages archived ?
        for m in self.msgs:
            if not getattr(m, ('sender' if m.sender == user else 'recipient') + '_archived'):
                archived = False
                break
        else:
            archived = True
        # look for the most recent received message (and non-deleted to comply with the future perms() control), if any
        for m in reversed(self.msgs):
            if m.recipient == user and not m.recipient_deleted_at:
                received = m
                break
        else:
            received = None
        context.update({
            'pm_messages': self.msgs,
            'archived': archived,
            'reply_to_pk': received.pk if received else None,
            'form': self.form_class(initial=received.quote(*self.formatters)) if received else None,
            'next_url': self.request.GET.get('next') or reverse('postman_inbox'),
        })
        return context

class MessageView(DisplayMixin, TemplateView):
    """Display one specific message."""

    def get(self, request, message_id, *args, **kwargs):
        self.filter = Q(pk=message_id)
        return super(MessageView, self).get(request, *args, **kwargs)

class ConversationView(DisplayMixin, TemplateView):
    """Display a conversation."""

    def get(self, request, thread_id, *args, **kwargs):
        self.filter = Q(thread=thread_id)
        return super(ConversationView, self).get(request, *args, **kwargs)

class UpdateMessageMixin(object):
    """
    Code common to the archive/delete/undelete actions.

    Attributes:
        ``field_bit``: a part of the name of the field to update
        ``success_msg``: the displayed text in case of success
    Optional attributes:
        ``field_value``: the value to set in the field
        ``success_url``: where to redirect to after a successful POST

    """
    http_method_names = ['post']
    field_value = None
    success_url = None

    @csrf_protect_m
    @login_required_m
    def dispatch(self, *args, **kwargs):
        return super(UpdateMessageMixin, self).dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        next_url = _get_referer(request) or 'postman_inbox'
        pks = request.POST.getlist('pks')
        tpks = request.POST.getlist('tpks')
        if pks or tpks:
            user = request.user
            filter = Q(pk__in=pks) | Q(thread__in=tpks)
            recipient_rows = Message.objects.as_recipient(user, filter).update(**{'recipient_{0}'.format(self.field_bit): self.field_value})
            sender_rows = Message.objects.as_sender(user, filter).update(**{'sender_{0}'.format(self.field_bit): self.field_value})
            if not (recipient_rows or sender_rows):
                raise Http404  # abnormal enough, like forged ids
            messages.success(request, self.success_msg, fail_silently=True)
            return redirect(request.GET.get('next') or self.success_url or next_url)
        else:
            messages.warning(request, _("Select at least one object."), fail_silently=True)
            return redirect(next_url)

class ArchiveView(UpdateMessageMixin, View):
    """Mark messages/conversations as archived."""
    field_bit = 'archived'
    success_msg = lz_("Messages or conversations successfully archived.")
    field_value = True

class DeleteView(UpdateMessageMixin, View):
    """Mark messages/conversations as deleted."""
    field_bit = 'deleted_at'
    success_msg = lz_("Messages or conversations successfully deleted.")
    field_value = now()

class UndeleteView(UpdateMessageMixin, View):
    """Revert messages/conversations from marked as deleted."""
    field_bit = 'deleted_at'
    success_msg = lz_("Messages or conversations successfully recovered.")
<|endoftext|>


<commit_before>"""Credentials management for cartoframes usage."""
import appdirs
import os
import json
import sys
import warnings
if sys.version_info >= (3, 0):
    from urllib.parse import urlparse
else:
    from urlparse import urlparse

_USER_CONFIG_DIR = appdirs.user_config_dir('cartoframes')
_DEFAULT_PATH = os.path.join(_USER_CONFIG_DIR, 'cartocreds.json')

class Credentials(object):
    """Credentials class for managing and storing user CARTO credentials. The
    arguments are listed in order of precedence: :obj:`Credentials` instances
    are first, `key` and `base_url`/`username` are taken next, and
    `config_file` (if given) is taken last. If no arguments are passed, then
    there will be an attempt to retrieve credentials from a previously saved
    session. One of the above scenarios needs to be met to successfully
    instantiate a :obj:`Credentials` object.

    Args:
        creds (:obj:`cartoframes.Credentials`, optional): Credentials instance
        key (str, optional): API key of user's CARTO account
        username (str, optional): Username of CARTO account
        base_url (str, optional): Base URL used for API calls. This is usually
            of the form `https://eschbacher.carto.com/` for user `eschbacher`.
            On premises installations (and others) have a different URL
            pattern.
        cred_file (str, optional): Pull credentials from a stored file. If this
            and all other args are not entered, Credentials will attempt to
            load a user config credentials file that was previously set with
            Credentials(...).save().

    Raises:
        RuntimeError: If not enough credential information is passed and no
            stored credentials file is found, this error will be raised.

    Example:

        .. code::

            from cartoframes.auth import Credentials, Context
            creds = Credentials(key='abcdefg', username='eschbacher')
            con = Context(creds=creds)

    """

    def __init__(self, username=None, =None, base_url=None, session=None):
        self._username = username
        self._api_key = api_key
        self._base_url = base_url or 'https://{}.carto.com/'.format(self._username)
        self._session = session

        self._norm_creds()

    @classmethod
    def create_from_file(cls, file=None):
        """Retrives credentials from a file. Defaults to the user config directory"""
        with open(config_file or _DEFAULT_PATH, 'r') as f:
            creds = json.load(f)

        return cls(creds.get('username'), creds.get('api_key'))

    def __repr__(self):
        return ('Credentials(username={username}, '
                'api_key={key}, '
                'base_url={base_url})').format(username=self._username,
                                               key=self._api_key,
                                               base_url=self._base_url)

    def __eq__(self, obj):
        return self._api_key == obj._api_key and self._username == obj._username and self._base_url == obj._base_url

    def _norm_creds(self):
        """Standardize credentials"""
        if self._base_url:
            self._base_url = self._base_url.strip('/')

    def save(self, config_file=None):
        """Saves current user credentials to user directory.

        Example:

            .. code::

                from cartoframes import Credentials
                creds = Credentials(username='eschbacher', key='abcdefg')
                creds.save()  # save to default location

        """

        if config_file is None:
            config_file = _DEFAULT_PATH

            """create directory if not exists"""
            if not os.path.exists(_USER_CONFIG_DIR):
                os.makedirs(_USER_CONFIG_DIR)

        with open(config_file, 'w') as f:
            json.dump({'username': self._username, 'api_key': self._api_key, 'base_url': self._base_url}, f)

    def delete(self, config_file=None):
        """Deletes the credentials file specified in `config_file`. If no
        file is specified, it deletes the default user credential file.

        Args:
            config_file (str): Path to configuration file. Defaults to delete
                the user default location if `None`.

        .. Tip::

            To see if there is a default user credential file stored, do the
            following::

                >>> creds = Credentials()
                >>> print(creds)
                Credentials(username=eschbacher, key=abcdefg,
                        base_url=https://eschbacher.carto.com/)

        """
        path_to_remove = config_file or _DEFAULT_PATH
        try:
            os.remove(path_to_remove)
            print('Credentials at {} successfully removed.'.format(
                path_to_remove))
        except OSError:
            warnings.warn('No credential file found at {}.'.format(
                path_to_remove))

    def set(self, key=None, username=None, base_url=None):
        """Update the credentials of a Credentials instance instead with new
        values.

        Args:
            key (str): API key of user account. Defaults to previous value if
                not specified.
            username (str): User name of account. This parameter is optional if
                `base_url` is not specified, but defaults to the previous
                value if not set.
            base_url (str): Base URL of user account. This parameter is
                optional if `username` is specified and on CARTO's
                cloud-based account. Generally of the form
                ``https://your_user_name.carto.com/`` for cloud-based accounts.
                If on-prem or otherwise, contact your admin.

        Example:

            .. code::

                from cartoframes import Credentials
                # load credentials saved in previous session
                creds = Credentials()
                # set new API key
                creds.set(key='new_api_key')
                # save new creds to default user config directory
                creds.save()

        Note:
            If the `username` is specified but the `base_url` is not, the
            `base_url` will be updated to ``https://<username>.carto.com/``.
        """
        self.__init__(key=(key or self._key),
                      username=(username or self._username),
                      base_url=base_url)

    def key(self, key=None):
        """Return or set API `key`.

        Args:
            key (str, optional): If set, updates the API key, otherwise returns
                current API key.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current API key
                >>> creds.key()
                'abcdefg'
                # updates API key with new value
                >>> creds.key('new_api_key')
        """
        if key:
            self._key = key
        else:
            return self._key

    def username(self, username=None):
        """Return or set `username`.

        Args:
            username (str, optional): If set, updates the `username`. Otherwise
                returns current `username`.

        Note:
            This does not update the `base_url` attribute. Use
            `Credentials.set` to have that updated with `username`.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current username
                >>> creds.username()
                'eschbacher'
                # updates username with new value
                >>> creds.username('new_username')
        """
        if username:
            self._username = username
        else:
            return self._username

    def base_url(self, base_url=None):
        """Return or set `base_url`.

        Args:
            base_url (str, optional): If set, updates the `base_url`. Otherwise
                returns current `base_url`.

        Note:
            This does not update the `username` attribute. Separately update
            the username with ``Credentials.username`` or update `base_url` and
            `username` at the same time with ``Credentials.set``.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current base_url
                >>> creds.base_url()
                'https://eschbacher.carto.com/'
                # updates base_url with new value
                >>> creds.base_url('new_base_url')
        """
        if base_url:
            # POSTs need to be over HTTPS (e.g., Import API reverts to a GET)
            if urlparse(base_url).scheme != 'https':
                raise ValueError(
                    '`base_url`s need to be over `https`. Update your '
                    '`base_url`.'
                )
            self._base_url = base_url
        else:
            return self._base_url
<commit_msg>create_from_credentials and constructor validation<commit_after>"""Credentials management for cartoframes usage."""
import appdirs
import os
import json
import sys
import warnings
if sys.version_info >= (3, 0):
    from urllib.parse import urlparse
else:
    from urlparse import urlparse

_USER_CONFIG_DIR = appdirs.user_config_dir('cartoframes')
_DEFAULT_PATH = os.path.join(_USER_CONFIG_DIR, 'cartocreds.json')

class Credentials(object):
    """Credentials class for managing and storing user CARTO credentials. The
    arguments are listed in order of precedence: :obj:`Credentials` instances
    are first, `key` and `base_url`/`username` are taken next, and
    `config_file` (if given) is taken last. If no arguments are passed, then
    there will be an attempt to retrieve credentials from a previously saved
    session. One of the above scenarios needs to be met to successfully
    instantiate a :obj:`Credentials` object.

    Args:
        creds (:obj:`cartoframes.Credentials`, optional): Credentials instance
        key (str, optional): API key of user's CARTO account
        username (str, optional): Username of CARTO account
        base_url (str, optional): Base URL used for API calls. This is usually
            of the form `https://eschbacher.carto.com/` for user `eschbacher`.
            On premises installations (and others) have a different URL
            pattern.
        cred_file (str, optional): Pull credentials from a stored file. If this
            and all other args are not entered, Credentials will attempt to
            load a user config credentials file that was previously set with
            Credentials(...).save().

    Raises:
        RuntimeError: If not enough credential information is passed and no
            stored credentials file is found, this error will be raised.

    Example:

        .. code::

            from cartoframes.auth import Credentials, Context
            creds = Credentials(key='abcdefg', username='eschbacher')
            con = Context(creds=creds)

    """

    def __init__(self, api_key, username=None, base_url=None, session=None):
        if username is None and base_url is None:
            raise ValueError('You must set at least a `username` or a `base_url` parameters')

        self._api_key = api_key
        self._username = username
        self._base_url = base_url or 'https://{}.carto.com/'.format(self._username)
        self._session = session

        self._norm_creds()

    @classmethod
    def create_from_file(cls, file=None):
        """Retrives credentials from a file. Defaults to the user config directory"""
        with open(config_file or _DEFAULT_PATH, 'r') as f:
            creds = json.load(f)

        return cls(creds.get('api_key'), creds.get('username'))

    @classmethod
    def create_from_credentials(cls, credentials):
        """Retrives credentials from another Credentials object"""
        if isinstance(creds, Credentials):
            return cls(credentials.api_key, credentials.username, credentials.base_url, credentials.session)

        raise ValueError('`credentials` must a Credentials class instance')

    def __repr__(self):
        return ('Credentials(username={username}, '
                'api_key={key}, '
                'base_url={base_url})').format(username=self._username,
                                               key=self._api_key,
                                               base_url=self._base_url)

    def __eq__(self, obj):
        return self._api_key == obj._api_key and self._username == obj._username and self._base_url == obj._base_url

    def _norm_creds(self):
        """Standardize credentials"""
        if self._base_url:
            self._base_url = self._base_url.strip('/')

    def save(self, config_file=None):
        """Saves current user credentials to user directory.

        Args:
            config_loc (str, optional): Location where credentials are to be
                stored. If no argument is provided, it will be send to the
                default location.

        Example:

            .. code::

                from cartoframes import Credentials
                creds = Credentials(username='eschbacher', key='abcdefg')
                creds.save()  # save to default location

            .. code::

                from cartoframes import Credentials
                creds = Credentials(username='eschbacher', key='abcdefg')
                creds.save('path/to/credentials/file')
        """

        if config_file is None:
            config_file = _DEFAULT_PATH

            """create directory if not exists"""
            if not os.path.exists(_USER_CONFIG_DIR):
                os.makedirs(_USER_CONFIG_DIR)

        with open(config_file, 'w') as f:
            json.dump({'username': self._username, 'api_key': self._api_key, 'base_url': self._base_url}, f)

    def delete(self, config_file=None):
        """Deletes the credentials file specified in `config_file`. If no
        file is specified, it deletes the default user credential file.

        Args:
            config_file (str): Path to configuration file. Defaults to delete
                the user default location if `None`.

        .. Tip::

            To see if there is a default user credential file stored, do the
            following::

                >>> creds = Credentials()
                >>> print(creds)
                Credentials(username=eschbacher, key=abcdefg,
                        base_url=https://eschbacher.carto.com/)

        """
        path_to_remove = config_file or _DEFAULT_PATH
        try:
            os.remove(path_to_remove)
            print('Credentials at {} successfully removed.'.format(
                path_to_remove))
        except OSError:
            warnings.warn('No credential file found at {}.'.format(
                path_to_remove))

    def set(self, key=None, username=None, base_url=None):
        """Update the credentials of a Credentials instance instead with new
        values.

        Args:
            key (str): API key of user account. Defaults to previous value if
                not specified.
            username (str): User name of account. This parameter is optional if
                `base_url` is not specified, but defaults to the previous
                value if not set.
            base_url (str): Base URL of user account. This parameter is
                optional if `username` is specified and on CARTO's
                cloud-based account. Generally of the form
                ``https://your_user_name.carto.com/`` for cloud-based accounts.
                If on-prem or otherwise, contact your admin.

        Example:

            .. code::

                from cartoframes import Credentials
                # load credentials saved in previous session
                creds = Credentials()
                # set new API key
                creds.set(key='new_api_key')
                # save new creds to default user config directory
                creds.save()

        Note:
            If the `username` is specified but the `base_url` is not, the
            `base_url` will be updated to ``https://<username>.carto.com/``.
        """
        self.__init__(key=(key or self._key),
                      username=(username or self._username),
                      base_url=base_url)

    def key(self, key=None):
        """Return or set API `key`.

        Args:
            key (str, optional): If set, updates the API key, otherwise returns
                current API key.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current API key
                >>> creds.key()
                'abcdefg'
                # updates API key with new value
                >>> creds.key('new_api_key')
        """
        if key:
            self._key = key
        else:
            return self._key

    def username(self, username=None):
        """Return or set `username`.

        Args:
            username (str, optional): If set, updates the `username`. Otherwise
                returns current `username`.

        Note:
            This does not update the `base_url` attribute. Use
            `Credentials.set` to have that updated with `username`.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current username
                >>> creds.username()
                'eschbacher'
                # updates username with new value
                >>> creds.username('new_username')
        """
        if username:
            self._username = username
        else:
            return self._username

    def base_url(self, base_url=None):
        """Return or set `base_url`.

        Args:
            base_url (str, optional): If set, updates the `base_url`. Otherwise
                returns current `base_url`.

        Note:
            This does not update the `username` attribute. Separately update
            the username with ``Credentials.username`` or update `base_url` and
            `username` at the same time with ``Credentials.set``.

        Example:

            .. code::

                >>> from cartoframes import Credentials
                # load credentials saved in previous session
                >>> creds = Credentials()
                # returns current base_url
                >>> creds.base_url()
                'https://eschbacher.carto.com/'
                # updates base_url with new value
                >>> creds.base_url('new_base_url')
        """
        if base_url:
            # POSTs need to be over HTTPS (e.g., Import API reverts to a GET)
            if urlparse(base_url).scheme != 'https':
                raise ValueError(
                    '`base_url`s need to be over `https`. Update your '
                    '`base_url`.'
                )
            self._base_url = base_url
        else:
            return self._base_url
<|endoftext|>