<commit_before>import os
import sys
import requests
import json
import yaml
import markdown
import codecs

if len(sys.argv) < 2:
    print "Please pass an API key as argument"
    exit()

API_KEY = sys.argv[1]
BASE_URL = 'https://docsapi.helpscout.net/v1'
SITE_ID = '54140576e4b005ed2d117482' # This is taken for example from the URL in the management console

CONTENT_ROOT = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 'content')

# https://pythonhosted.org/Markdown/extensions/index.html
MARKDOWN_EXTENSIONS = [
    'markdown.extensions.tables',
    'markdown.extensions.fenced_code'
]

HTTP_AUTH = (API_KEY, 'X')

session = requests.session()

CONTENT_TYPES = {
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.png': 'image/png'
}

def handle_request_error(method):
    def wrapper(*args, **kwargs):
        try:
            res = method(*args, **kwargs)
            return res
        except requests.exceptions.HTTPError as e:
            print e
            print e.response.content
            exit(0)

    return wrapper

@handle_request_error
def api_get(entrypoint, params=None):
    res = session.get('%s/%s' % (BASE_URL, entrypoint), params=params, auth=HTTP_AUTH)
    res.raise_for_status()
    return res.json()

@handle_request_error
def api_post_json(entrypoint, payload, params=None):
    json_payload = json.dumps(payload)
    res = session.post('%s/%s' % (BASE_URL, entrypoint), params=params, data=json_payload, auth=HTTP_AUTH, headers={'Content-Type': 'application/json'})
    res.raise_for_status()
    return res

@handle_request_error
def api_put_json(entrypoint, payload, params=None):
    json_payload = json.dumps(payload)
    res = session.put('%s/%s' % (BASE_URL, entrypoint), params=params, data=json_payload, auth=HTTP_AUTH, headers={'Content-Type': 'application/json'})
    res.raise_for_status()
    return res

@handle_request_error
def api_post_file(entrypoint, file, payload, params=None):
    res = session.post('%s/%s' % (BASE_URL, entrypoint), files=file, data=payload, params=params, auth=HTTP_AUTH)
    res.raise_for_status()
    return res

@handle_request_error
def api_delete(entrypoint):
    res = session.delete('%s/%s' % (BASE_URL, entrypoint), auth=HTTP_AUTH)
    res.raise_for_status()
    return res

def load_metadata(path):
    """
    Loads the metadata from the metadata.yaml file in the folder
    """
    metadata_path = os.path.join(path, 'metadata.yaml')
    if os.path.exists(metadata_path):
        return yaml.load(open(metadata_path))
    else:
        return {}

def update_or_create(entrypoint, item_type, data, id=None):
    if id:
        # PUT
        return api_put_json(entrypoint+'/'+id, data, params={'reload': 'true'}).json()[item_type]['id']
    else:
        # POST
        return api_post_json(entrypoint, data, params={'reload': 'true'}).json()[item_type]['id']

def retrieve_existing_items(entrypoint, params=None):
    return {item['name']: item['id'] for item in api_get(entrypoint, params=params)[entrypoint.split('/')[-1]]['items']}

def purge_obsolete(entrypoint, item_type, existing_remote_items, local_names):
    for remote_item_name, remote_item_id in existing_remote_items.items():
        if not remote_item_name in local_names:
            print 'Deleting %s %s' % (item_type, remote_item_name)
            api_delete('%s/%s' % (entrypoint, remote_item_id))

def push_documentation():
    # At each level we retrieve the list of existing items and create an associative array  (Name => HelpScout ID)
    # This way we know if what we want to post already exists (in that case we update it)
    # It also allows us to purge the existing items that don't exist anymore
    existing_collections = retrieve_existing_items('collections', params={'siteId': SITE_ID})
    local_collections = []

    for collection_name in os.listdir(CONTENT_ROOT):
        collection_path = os.path.join(CONTENT_ROOT, collection_name)
        collection_metadata = load_metadata(collection_path)
        collection_title = collection_metadata.get('title', collection_name)
        if os.path.isdir(collection_path):
            local_collections.append(collection_title)
            existing_id = existing_collections.get(collection_title)
            if existing_id:
                print 'Updating collection %s' % collection_title
            else:
                print 'Creating collection %s' % collection_title
            collection_id = update_or_create('collections', 'collection', {
                'siteId': SITE_ID,
                'name': collection_title,
                'order': collection_metadata.get('order', 1)
            }, id=existing_id)

            existing_categories = retrieve_existing_items('collections/%s/categories' % collection_id)
            local_categories = ['Uncategorized']

            # Categories
            for category_name in os.listdir(collection_path):
                category_path = os.path.join(collection_path, category_name)
                category_metadata = load_metadata(category_path)
                category_title = category_metadata.get('title', category_name)
                if os.path.isdir(category_path):
                    local_categories.append(category_title)
                    existing_id = existing_categories.get(category_title)
                    if existing_id:
                        print '\tUpdating category %s' % category_title
                    else:
                        print '\tCreating category %s' % category_title
                    category_id = update_or_create('categories', 'category', {
                        'collectionId': collection_id,
                        'name': category_title,
                        'order': category_metadata.get('order', 1)
                    }, id=existing_id)

                    existing_articles = retrieve_existing_items('categories/%s/articles' % category_id)
                    local_articles = []

                    # Articles
                    for article_name in os.listdir(category_path):
                        article_path = os.path.join(category_path, article_name)
                        article_metadata = load_metadata(article_path)
                        article_title = article_metadata.get('title', article_name)
                        if os.path.isdir(article_path):
                            local_articles.append(article_title)
                            existing_id = existing_articles.get(article_title)
                            if existing_id:
                                print '\t\tUpdating article %s' % article_title
                            else:
                                print '\t\tCreating article %s' % article_title
                            with codecs.open(os.path.join(article_path, 'article.md'), "r", "utf-8") as article_file:
                                article_content = article_file.read()

                            article_content_html = markdown.markdown(article_content, extensions=MARKDOWN_EXTENSIONS)
                            article_id = update_or_create('articles', 'article', {
                                'collectionId': collection_id,
                                'categories': [category_id],
                                'name': article_title,
                                'status': 'published',
                                'text': article_content_html
                                }, id=existing_id)

                            # Upload the assets if they exist
                            images_path = os.path.join(article_path, 'images')
                            if os.path.exists(images_path) and os.path.isdir(images_path):
                                images = {}
                                # Upload each image
                                for image in os.listdir(images_path):
                                    image_url = api_post_file('assets/article', {
                                        'file': (image, open(os.path.join(images_path, image), 'rb'), CONTENT_TYPES.get(os.path.splitext(image)[1], None))
                                    }, {
                                        'key': API_KEY,
                                        'articleId': article_id,
                                        'assetType': 'image'
                                    }).json()['filelink']
                                    images[image] = image_url
                                # Replace the occurences with the real URL
                                for original_image, image_url in images.items():
                                    article_content_html = article_content_html.replace(' src="%s" />' % original_image, ' src="%s" />' % image_url)
                                # Update the article content with the new images
                                api_put_json('articles/%s' % article_id, {
                                    'text': article_content_html
                                })
                    purge_obsolete('articles', 'article', existing_articles, local_articles)
            purge_obsolete('categories', 'category', existing_categories, local_categories)
    purge_obsolete('collections', 'collection', existing_collections, local_collections)

if __name__ == '__main__':
    push_documentation()
<commit_msg>HelpScout considers names to be unique in a case-insensitive manner<commit_after>import os
import sys
import requests
import json
import yaml
import markdown
import codecs

if len(sys.argv) < 2:
    print "Please pass an API key as argument"
    exit()

API_KEY = sys.argv[1]
BASE_URL = 'https://docsapi.helpscout.net/v1'
SITE_ID = '54140576e4b005ed2d117482' # This is taken for example from the URL in the management console

CONTENT_ROOT = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 'content')

# https://pythonhosted.org/Markdown/extensions/index.html
MARKDOWN_EXTENSIONS = [
    'markdown.extensions.tables',
    'markdown.extensions.fenced_code'
]

HTTP_AUTH = (API_KEY, 'X')

session = requests.session()

CONTENT_TYPES = {
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.png': 'image/png'
}

def handle_request_error(method):
    def wrapper(*args, **kwargs):
        try:
            res = method(*args, **kwargs)
            return res
        except requests.exceptions.HTTPError as e:
            print e
            print e.response.content
            exit(0)

    return wrapper

@handle_request_error
def api_get(entrypoint, params=None):
    res = session.get('%s/%s' % (BASE_URL, entrypoint), params=params, auth=HTTP_AUTH)
    res.raise_for_status()
    return res.json()

@handle_request_error
def api_post_json(entrypoint, payload, params=None):
    json_payload = json.dumps(payload)
    res = session.post('%s/%s' % (BASE_URL, entrypoint), params=params, data=json_payload, auth=HTTP_AUTH, headers={'Content-Type': 'application/json'})
    res.raise_for_status()
    return res

@handle_request_error
def api_put_json(entrypoint, payload, params=None):
    json_payload = json.dumps(payload)
    res = session.put('%s/%s' % (BASE_URL, entrypoint), params=params, data=json_payload, auth=HTTP_AUTH, headers={'Content-Type': 'application/json'})
    res.raise_for_status()
    return res

@handle_request_error
def api_post_file(entrypoint, file, payload, params=None):
    res = session.post('%s/%s' % (BASE_URL, entrypoint), files=file, data=payload, params=params, auth=HTTP_AUTH)
    res.raise_for_status()
    return res

@handle_request_error
def api_delete(entrypoint):
    res = session.delete('%s/%s' % (BASE_URL, entrypoint), auth=HTTP_AUTH)
    res.raise_for_status()
    return res

def load_metadata(path):
    """
    Loads the metadata from the metadata.yaml file in the folder
    """
    metadata_path = os.path.join(path, 'metadata.yaml')
    if os.path.exists(metadata_path):
        return yaml.load(open(metadata_path))
    else:
        return {}

def update_or_create(entrypoint, item_type, data, id=None):
    if id:
        # PUT
        return api_put_json(entrypoint+'/'+id, data, params={'reload': 'true'}).json()[item_type]['id']
    else:
        # POST
        return api_post_json(entrypoint, data, params={'reload': 'true'}).json()[item_type]['id']

def retrieve_existing_items(entrypoint, params=None):
    return {item['name'].lower(): item['id'] for item in api_get(entrypoint, params=params)[entrypoint.split('/')[-1]]['items']}

def purge_obsolete(entrypoint, item_type, existing_remote_items, local_names):
    # Item names need to be compared case-insensitively, because HelpScout considers them unique
    local_names = [name.lower() for name in local_names]
    for remote_item_name, remote_item_id in existing_remote_items.items():
        if not remote_item_name.lower() in local_names:
            print 'Deleting %s %s' % (item_type, remote_item_name)
            api_delete('%s/%s' % (entrypoint, remote_item_id))

def push_documentation():
    # At each level we retrieve the list of existing items and create an associative array  (Name => HelpScout ID)
    # This way we know if what we want to post already exists (in that case we update it)
    # It also allows us to purge the existing items that don't exist anymore
    existing_collections = retrieve_existing_items('collections', params={'siteId': SITE_ID})
    local_collections = []

    for collection_name in os.listdir(CONTENT_ROOT):
        collection_path = os.path.join(CONTENT_ROOT, collection_name)
        collection_metadata = load_metadata(collection_path)
        collection_title = collection_metadata.get('title', collection_name)
        if os.path.isdir(collection_path):
            local_collections.append(collection_title)
            existing_id = existing_collections.get(collection_title.lower())
            if existing_id:
                print 'Updating collection %s' % collection_title
            else:
                print 'Creating collection %s' % collection_title
            collection_id = update_or_create('collections', 'collection', {
                'siteId': SITE_ID,
                'name': collection_title,
                'order': collection_metadata.get('order', 1)
            }, id=existing_id)

            existing_categories = retrieve_existing_items('collections/%s/categories' % collection_id)
            local_categories = ['Uncategorized']

            # Categories
            for category_name in os.listdir(collection_path):
                category_path = os.path.join(collection_path, category_name)
                category_metadata = load_metadata(category_path)
                category_title = category_metadata.get('title', category_name)
                if os.path.isdir(category_path):
                    local_categories.append(category_title)
                    existing_id = existing_categories.get(category_title.lower())
                    if existing_id:
                        print '\tUpdating category %s' % category_title
                    else:
                        print '\tCreating category %s' % category_title
                    category_id = update_or_create('categories', 'category', {
                        'collectionId': collection_id,
                        'name': category_title,
                        'order': category_metadata.get('order', 1)
                    }, id=existing_id)

                    existing_articles = retrieve_existing_items('categories/%s/articles' % category_id)
                    local_articles = []

                    # Articles
                    for article_name in os.listdir(category_path):
                        article_path = os.path.join(category_path, article_name)
                        article_metadata = load_metadata(article_path)
                        article_title = article_metadata.get('title', article_name)
                        if os.path.isdir(article_path):
                            local_articles.append(article_title)
                            existing_id = existing_articles.get(article_title.lower())
                            if existing_id:
                                print '\t\tUpdating article %s' % article_title
                            else:
                                print '\t\tCreating article %s' % article_title
                            with codecs.open(os.path.join(article_path, 'article.md'), "r", "utf-8") as article_file:
                                article_content = article_file.read()

                            article_content_html = markdown.markdown(article_content, extensions=MARKDOWN_EXTENSIONS)
                            article_id = update_or_create('articles', 'article', {
                                'collectionId': collection_id,
                                'categories': [category_id],
                                'name': article_title,
                                'status': 'published',
                                'text': article_content_html
                                }, id=existing_id)

                            # Upload the assets if they exist
                            images_path = os.path.join(article_path, 'images')
                            if os.path.exists(images_path) and os.path.isdir(images_path):
                                images = {}
                                # Upload each image
                                for image in os.listdir(images_path):
                                    image_url = api_post_file('assets/article', {
                                        'file': (image, open(os.path.join(images_path, image), 'rb'), CONTENT_TYPES.get(os.path.splitext(image)[1], None))
                                    }, {
                                        'key': API_KEY,
                                        'articleId': article_id,
                                        'assetType': 'image'
                                    }).json()['filelink']
                                    images[image] = image_url
                                # Replace the occurences with the real URL
                                for original_image, image_url in images.items():
                                    article_content_html = article_content_html.replace(' src="%s" />' % original_image, ' src="%s" />' % image_url)
                                # Update the article content with the new images
                                api_put_json('articles/%s' % article_id, {
                                    'text': article_content_html
                                })
                    purge_obsolete('articles', 'article', existing_articles, local_articles)
            purge_obsolete('categories', 'category', existing_categories, local_categories)
    purge_obsolete('collections', 'collection', existing_collections, local_collections)

if __name__ == '__main__':
    push_documentation()
<|endoftext|>


<commit_before>import common.constant as c
import itertools
import os

from common.base import *
from django.test import TestCase
from django.shortcuts import get_object_or_404, get_list_or_404
from api.celery_tasks import *
from api.serializers import TaskSerializer, JobSerializer
from api.models import Task, Job
from collections import namedtuple

class TestFull(TestCase, Base):

	def setUp(self):

		self.data_dir = os.path.join(c.TEST_FILE_PATH, 'exp')
		# epsilon.1
		self.eps1_levels = [5]

		# noises
		self.privacy_levels = [1,2,3,4,5]

		# k
		self.k_val = [0]

		# number of runs
		self.nrun = 1

		# test cases
		self.cases = [
			(
				"data2",
				[]
			)
		]

		# specified data domain
		self.specified_data_domain = False

		# specify the chunk size, -1 to be the whole dataset
		self.chunk_size = 100000

	def get_eps(self, level):
		corr = {
			1: 0.01,
			2: 0.1,
			3: 1,
			4: 10,
			5: 100
		}
		return corr[level]

	def get_esp_1(self, level):
		corr = {
			1:0.05,
			2:0.5,
			3:5,
			4:50,
			5:700
		}
		return corr[level]

	def test_full(self):
		cnt = 0
		for data_name, white_list in self.cases:
			domain = self.read_domain_file(data_name)
			cnt += 1
			self.diff_privacy(cnt, data_name, domain, self.privacy_levels, white_list = white_list)
	"""
	def diff_privacy(self, data_name, domain, eps_ls, white_list = []):
		data_input = {
			"task_id": 1,
			"task_name": "%s.dat" % data_name,
			"data_path": "%s/%s.dat" % (self.data_dir, data_name),
			"selected_attrs": domain,
			"white_list": white_list,
			"names": domain['names'],
			"chunk_size": self.chunk_size,
			"unitest":True
		}
		create_process(data_input)
		pass
	"""

	def diff_privacy(self, case_num, data_name, domain, eps_ls, white_list = []):
		data_input = {
			"task_id": case_num,
			"task_name": "%s.dat" % data_name,
			"data_path": "%s/%s.dat" % (self.data_dir, data_name),
			"selected_attrs": domain,
			"white_list": white_list,
			"names": domain['names'],
			"chunk_size": self.chunk_size,
			"unitest":True
		}

		comb_eps2_k = list(itertools.product(self.privacy_levels, self.k_val))
		for eps1_lv in self.eps1_levels:

			for i in range(self.nrun):
				data_input['eps1_level'] = eps1_lv
				data_input['eps1_val'] = self.get_esp_1(eps1_lv)

				task = create_process(data_input)
				for (eps2_lv, k) in comb_eps2_k:
					privacy_input = {
						"dp_id":1,
						"privacy_level":eps2_lv,
						"epsilon":self.get_eps(eps2_lv),
						"task_id": task,
						"exp_round":i+1,
						"min_freq":k,
						"unitest": True
					}
					create_anonymity(privacy_input)

	def read_domain_file(self, data_name):
		import re
		file_pattern = '%s.domain' % data_name
		domain_path = os.path.join(self.data_dir, file_pattern)
		names = []
		dtypes = []
		specified_c_domain = dict()
		date_format = dict()
		with open(domain_path, 'r') as domain:
			content = domain.readline()
			while len(content) > 0:
				splited_line = re.split('\s+', content.strip())

				if splited_line[1] not in ['C', 'D', 'T']:
					content = domain.readline()
					continue

				if splited_line[1] is 'C':
					specified_c_domain[splited_line[0]] = splited_line[3:]

				if splited_line[1] is 'T':
					date_format[splited_line[0]] = splited_line[2:]

				names.append(splited_line[0])
				dtypes.append(splited_line[1])
				content = domain.readline()

		if self.specified_data_domain is True:
			return {'names': names, 'types': dtypes, 'specified_c_domain': specified_c_domain, 'date_format': date_format}

		return {'names': names, 'types': dtypes, 'date_format': date_format}
<commit_msg>test case<commit_after>import common.constant as c
import itertools
import os

from common.base import *
from django.test import TestCase
from django.shortcuts import get_object_or_404, get_list_or_404
from api.celery_tasks import *
from api.serializers import TaskSerializer, JobSerializer
from api.models import Task, Job
from collections import namedtuple

class TestFull(TestCase, Base):

	def setUp(self):

		self.data_dir = os.path.join(c.TEST_FILE_PATH, 'exp')
		# epsilon.1
		self.eps1_levels = [5]

		# noises
		self.privacy_levels = [1,3,5]

		# k
		self.k_val = [0]

		# number of runs
		self.nrun = 1

		# test cases
		self.cases = [
			(
				"data_fin_title.dat",
				[]
			),
			(
				"data_fin_title.dat",
				[['stk', 'dd', 'sc']]
			)
		]

		# specified data domain
		self.specified_data_domain = False

		# specify the chunk size, -1 to be the whole dataset
		self.chunk_size = 100000

	def get_eps(self, level):
		corr = {
			1: 0.01,
			2: 0.1,
			3: 1,
			4: 10,
			5: 100
		}
		return corr[level]

	def get_esp_1(self, level):
		corr = {
			1:0.05,
			2:0.5,
			3:5,
			4:50,
			5:700
		}
		return corr[level]

	def test_full(self):
		cnt = 0
		for data_name, white_list in self.cases:
			domain = self.read_domain_file(data_name)
			cnt += 1
			self.diff_privacy(cnt, data_name, domain, self.privacy_levels, white_list = white_list)
	"""
	def diff_privacy(self, data_name, domain, eps_ls, white_list = []):
		data_input = {
			"task_id": 1,
			"task_name": "%s.dat" % data_name,
			"data_path": "%s/%s.dat" % (self.data_dir, data_name),
			"selected_attrs": domain,
			"white_list": white_list,
			"names": domain['names'],
			"chunk_size": self.chunk_size,
			"unitest":True
		}
		create_process(data_input)
		pass
	"""

	def diff_privacy(self, case_num, data_name, domain, eps_ls, white_list = []):
		data_input = {
			"task_id": case_num,
			"task_name": "%s.dat" % data_name,
			"data_path": "%s/%s.dat" % (self.data_dir, data_name),
			"selected_attrs": domain,
			"white_list": white_list,
			"names": domain['names'],
			"chunk_size": self.chunk_size,
			"unitest":True
		}

		comb_eps2_k = list(itertools.product(self.privacy_levels, self.k_val))
		for eps1_lv in self.eps1_levels:

			for i in range(self.nrun):
				data_input['eps1_level'] = eps1_lv
				data_input['eps1_val'] = self.get_esp_1(eps1_lv)

				task = create_process(data_input)
				for (eps2_lv, k) in comb_eps2_k:
					privacy_input = {
						"dp_id":1,
						"privacy_level":eps2_lv,
						"epsilon":self.get_eps(eps2_lv),
						"task_id": task,
						"exp_round":i+1,
						"min_freq":k,
						"unitest": True
					}
					create_anonymity(privacy_input)

	def read_domain_file(self, data_name):
		import re
		file_pattern = '%s.domain' % data_name
		domain_path = os.path.join(self.data_dir, file_pattern)
		names = []
		dtypes = []
		specified_c_domain = dict()
		date_format = dict()
		with open(domain_path, 'r') as domain:
			content = domain.readline()
			while len(content) > 0:
				splited_line = re.split('\s+', content.strip())

				if splited_line[1] not in ['C', 'D', 'T']:
					content = domain.readline()
					continue

				if splited_line[1] is 'C':
					specified_c_domain[splited_line[0]] = splited_line[3:]

				if splited_line[1] is 'T':
					date_format[splited_line[0]] = splited_line[2:]

				names.append(splited_line[0])
				dtypes.append(splited_line[1])
				content = domain.readline()

		if self.specified_data_domain is True:
			return {'names': names, 'types': dtypes, 'specified_c_domain': specified_c_domain, 'date_format': date_format}

		return {'names': names, 'types': dtypes, 'date_format': date_format}
<|endoftext|>


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

import sys
import re
import numpy as np
import matplotlib.pyplot as plt

with open(sys.argv[1], 'r') as myfile:
    data = myfile.read().strip()

    tests = [ [ float(cost) for cost in line.split(',') ] for line in data.split('\n') ]

    plt.boxplot(tests)
    plt.show()
<commit_msg>Output box plot data in script<commit_after>#!/usr/bin/env python3

import sys
import re
import numpy as np
import matplotlib.pyplot as plt

with open(sys.argv[1], 'r') as myfile:
    data = myfile.read().strip()

    tests = [ [ float(cost) for cost in line.split(',') ] for line in data.split('\n') ]

    bp = plt.boxplot(tests)

    for plot, line in enumerate(bp['medians']):
        linedata = line.get_ydata()
        for value in linedata:
            print("Median " + str(plot) + ": " + str(value))

    for plot, line in enumerate(bp['whiskers']):
        linedata = line.get_ydata()
        for value in linedata:
            print("Box " + str(int(plot/2)) + " whisker " + str(plot % 2) + ": " + str(value))

    plt.show()
<|endoftext|>


<commit_before>#  -------------------------------------------------------------------------
#  Author: Roberto Cavada <roboogle@gmail.com>
#
#  Copyright (C) 2006 by Roberto Cavada
#
#  pygtkmvc is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
#
#  pygtkmvc is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
#  Boston, MA 02110, USA.
#
#  For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
#  or email to the author Roberto Cavada <roboogle@gmail.com>.
#  Please report bugs to <roboogle@gmail.com>.
#  -------------------------------------------------------------------------

from support import decorators, utils, log

import inspect
import types

@decorators.good_decorator_accepting_args
def observes(*args):
    """
    Decorate a method in an :class:`Observer` subclass as a notification.
    Takes one to many property names as strings. If any of them changes
    in a model we observe, the method is called. If multiple names were given
    the method will be passed the name of the property that changed.

       .. versionadded:: 1.99.0

       .. deprecated:: 1.99.1
          Use :meth:`Observer.observes` instead.
    """

    @decorators.good_decorator
    def _decorator(_notified):
        # marks the method with observed properties
        _set = getattr(_notified, Observer._CUST_OBS_, set())
        _set |=  set(args)
        setattr(_notified, Observer._CUST_OBS_, _set)
        return _notified

    log.logger.warning("Decorator observer.observers is deprecated:"
                       "use Observer.observes instead")
    return _decorator
# ----------------------------------------------------------------------

# private metaclass, used to force read-only class-level properties.
class __NTinfo_mc__ (type):
    NT_AUTO  = property(lambda cls: 0) # for internal use only
    NT_VALUE  = property(lambda cls: 1)
    NT_BEFORE = property(lambda cls: 2)
    NT_AFTER  = property(lambda cls: 4)
    NT_SIGNAL = property(lambda cls: 8)
    pass # end of metaclass

class NTInfo (dict):
    """Notification Type of observers' notification methods.

    This class is used:

    1. As class when defining notificaion methods in observers, as it
       contains the constants for notification types.

    2. As class instance as parameter when a notification methods is
       called in observers.

    Notification Type Constants
    ===========================

    Class `NTInfo` exports these notification type constants:

    NT_VALUE: For value-change notification types.
    NT_BEFORE: For before-method-call notification types.
    NT_AFTER: For after-method-call notification types.
    NT_SIGNAL: For signal-emit notification types.

    Instance content
    ================

    Instances of class `NTInfo` will be received as the last argument
    (`info`) of any notification method::

      def notification_method(self, model, name, info)

    NTInfo is a dictionary (with some particular behaviour added),
    containing some information which is independent on the
    notification type, and some other information wich depends on the
    notification type.

    Common to all types
    -------------------

    For all notification types, NTInfo contains:

      model : the model containing the observable property triggering
              the notification. model is also passed as first argument
              of the notification method.

      name : the name of the observable property triggering the
             notification. name is also passed as second argument of
             the notification method.

      type : The value identifying the type of the notification
             method. This is generally one value among NT_VALUE,
             NT_BEFORE, NT_AFTER and NT_SIGNAL, but more generally it
             can be also a bitwise combination of these values.

    There are further information depending on the specific
    notification type:

    For NT_VALUE
    ------------

      old : the value that the observable property had before being
            changed.

      new : the new value that the observable property has been
            changed to.

    For NT_BEFORE
    -------------

      instance : the object instance which the method that is being
                 called belongs to.

      method_name : the name of the method that is being called.

      args : tuple of the arguments of the method that is being called.

      kwargs: dictionary of the keyword arguments of the method that
              is being called.

    For NT_AFTER
    ------------

      instance : the object instance which the method that has been
                 called belongs to.

      method_name : the name of the method that has been called.

      args : tuple of the arguments of the method that has been called.

      kwargs: dictionary of the keyword arguments of the method that
              has been called.

      result : the value returned by the method which has been called.

    For NT_SIGNAL
    -------------

      arg : the argument which was optionally specified when invoking
            emit() on the signal observable property.

    Type queries
    ============

    As already told, the type carried by a NTInfo instance can be
    accessed through attribute `type`, to be matched type constants
    available in class `NTInfo`, possibly with bit masks.  However,
    class `NTInfo` provides also predicates to query for type, like
    methods `is_value`, `is_before`, etc.

       .. versionadded:: 1.99.1

    """

    __metaclass__ = _NTinfo_mc

    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        # if not already set, sets the default type
        if 'type' not in self: self['type'] = NTInfo.NT_VALUE
        return

    def __getattr__(self, name): return self[name]

    def is_value(self):
        return self['type'] & NTInfo.NT_VALUE != 0

    def is_before(self):
        return self['type'] & NTInfo.NT_BEFORE != 0

    def is_after(self):
        return self['type'] & NTInfo.NT_AFTER != 0

    def is_signal(self):
        return self['type'] & NTInfo.NT_AFTER != 0

    pass # end of class

# ----------------------------------------------------------------------
class Observer (object):
    """
    .. note::

       Most methods in this class are used internally by the framework.
       Do not override them in subclasses.
    """

    # these are internal
    _CUST_OBS_ = "__custom_observes__"

    @classmethod
    @decorators.good_decorator_accepting_args
    def observes(cls, *args, **kwargs):
        """
        Decorate a method as a notification. Takes an arbitrary number
        of property names as strings. If none are given, the name of
        the method is used. If a given property changes in a model we
        observe, the method is called.

        Keyword argument 'type' can be used to specify the type of the
        notification. Values for type are type constants in class
        `NTInfo`.  Values for `type` can be combined for declaring
        notifications which can receive notification of several
        types. If `type` is not specified, the default value is
        `NTInfo.NT_VALUE`.

           .. versionadded:: 1.99.1
        """

        @decorators.good_decorator
        def _decorator(_notified):
            # marks the method with observed properties
            _set = getattr(_notified, Observer._CUST_OBS_, set())
            _set |=  set(names)
            setattr(_notified, Observer._CUST_OBS_, _set)
            return _notified

        # this handle the case of empty args
        if 1 == len(args) and type(args[0]) is types.FunctionType:
            names = ()
            return _decorator(args[0])

        # this is the case with arguments
        names = args
        return _decorator
    # ----------------------------------------------------------------------

    def __init__(self, model=None, spurious=False):
        """
        *model* is passed to :meth:`observe_model` if given.

        *spurious* indicates interest to be notified even when
        the value hasn't changed, like for: ::

         model.prop = model.prop

        .. versionadded:: 1.2.0
           Before that observers had to filter out spurious
           notifications themselves, as if the default was `True`. With
           :class:`~gtkmvc.observable.Signal` support this is no longer
           necessary.
        """

        self.__accepts_spurious__ = spurious

        # NOTE: In rev. 202 these maps were unified into
        #   __CUST_OBS_MAP only (the map contained pairs (method,
        #   args). However, this broke backward compatibility of code
        #   accessing the map through
        #   get_observing_methods. Now the informatio is split
        #   and the original information restored. To access the
        #   additional information (number of additional arguments
        #   required by observing methods) use the newly added methods.

        # Private maps: do not change/access them directly, use
        # methods to access them:
        self.__CUST_OBS_MAP = {} # prop name --> set of observing methods
        self.__CUST_OBS_ARGS = {} # observing method --> flag

        processed_props = set() # tracks already processed properties

        # searches all custom observer methods
        for cls in inspect.getmro(type(self)):
            # list of (method-name, method-object, list-of-properties-the-method-observes)
            meths = [ (name, meth, getattr(meth, Observer._CUST_OBS_))
                      for name, meth in cls.__dict__.iteritems()
                      if inspect.isfunction(meth) and hasattr(meth, Observer._CUST_OBS_)]

            # props processed in this class. This is used to avoid
            # processing the same props in base classes.
            cls_processed_props = set()

            # TODO: fix this code to call add_observing_method

            # since this is traversed top-bottom in the mro, the
            # first found match is the one to care
            for name, meth, pnames in meths:
                if 0 == len(pnames):
                    # no args were specified in the decorator. In this
                    # case the method does not take the prop name among
                    # its arguments, and the property name is taken
                    # from the method name.
                    if name not in processed_props:
                        if not self.__CUST_OBS_MAP.has_key(name):
                            self.__CUST_OBS_MAP[name] = set()
                            pass
                        _me = getattr(self, name)
                        self.__CUST_OBS_MAP[name].add(_me)

                        # first hit matters
                        if not self.__CUST_OBS_ARGS.has_key(_me):
                            self.__CUST_OBS_ARGS[_me] = False # no additional args
                            pass
                        cls_processed_props.add(name)
                        pass
                    pass
                else:
                    takes_arg = 1 < len(pnames)
                    for prop_name in pnames:
                        if prop_name not in processed_props:
                            if not self.__CUST_OBS_MAP.has_key(prop_name):
                                self.__CUST_OBS_MAP[prop_name] = set()
                                pass
                            _me = getattr(self, name)
                            self.__CUST_OBS_MAP[prop_name].add(_me)

                            # first hit matters
                            if not self.__CUST_OBS_ARGS.has_key(_me):
                                self.__CUST_OBS_ARGS[_me] = takes_arg
                                pass

                            cls_processed_props.add(prop_name)
                            pass
                        pass
                    pass
                pass

            # accumulates props processed in this class
            processed_props |= cls_processed_props
            pass # end of loop over classes in the mro

        if model: self.observe_model(model)
        return

    def observe_model(self, model):
        """
        Call :meth:`~gtkmvc.model.Model.register_observer` on *model*.
        """
        return model.register_observer(self)

    def relieve_model(self, model):
        """
        Call :meth:`~gtkmvc.model.Model.unregister_observer` on *model*.
        """
        return model.unregister_observer(self)

    def accepts_spurious_change(self):
        """
        Are we interested in spurious notifications?

        :rtype: bool
        """
        return self.__accepts_spurious__

    def add_observing_method(self, method,
                             prop_name_or_names):
        """
        Add notifications at runtime.

        *method* is a callable bound to `self`.

        *prop_name_or_names* can be a string or a sequence of strings.
        In the latter case the name of the property that changed will be
        passed to the method during notifications.

        Multiple calls may add different methods for the same names as well as
        additional names for the same method. However, the signature of the
        method is always determined by the last call. ::

         def observes_one_property(self, model, old, new)

         def observes_multiple(self, model, prop_name, old, new)

        .. note::

           Dynamic methods only work if you add/remove them before starting
           to observe a model.
        """

        if isinstance(prop_name_or_names, types.StringType):
            takes_name = False
            prop_names = (prop_name_or_names,)
        else:
            try:
                iter(prop_name_or_names)
            except TypeError:
                raise AssertionError("prop_name_or_names must be either a"
                    " string or an iterable")
            takes_name = True
            prop_names = prop_name_or_names
            pass

        for prop_name in prop_names:
            if not self.__CUST_OBS_MAP.has_key(prop_name):
                self.__CUST_OBS_MAP[prop_name] = set()
                pass
            self.__CUST_OBS_MAP[prop_name].add(method)
            pass
        self.__CUST_OBS_ARGS[method] = takes_name
        return

    def remove_observing_method(self, method, prop_names):
        """
        Remove dynamic notifications.

        *method* a callable that was registered with
        :meth:`add_observing_method` or :meth:`observes`.

        *prop_names* a sequence of strings. This need not correspond to any
        one `add` call.

        .. note::

           This can revert the effects of a decorator at runtime. Don't.
        """
        for prop_name in prop_names:
            _set = self.__CUST_OBS_MAP.get(prop_name, set())
            if method in _set: _set.remove(method)
            pass

        if method in self.__CUST_OBS_ARGS: del self.__CUST_OBS_ARGS[method]
        return

    def is_observing_method(self, method):
        """
        Returns `True` if the given method was previously added as an
        observing method, either dynamically or via decorator.
        """
        return self.__CUST_OBS_ARGS.has_key(method)

    def get_observing_methods(self, prop_name):
        """
        Return a possibly empty set of callables registered with
        :meth:`add_observing_method` or :meth:`observes` for *prop_name*.

        .. versionadded:: 1.99.1
           Replaces :meth:`get_custom_observing_methods`.
        """
        return self.__CUST_OBS_MAP.get(prop_name, set())

    # this is done to keep backward compatibility
    get_custom_observing_methods = get_observing_methods

    def does_observing_method_receive_prop_name(self, method):
        """
        Will the method be passed the name of the property that changed on
        notification? This additional argument is meant for methods that are
        registered for more than one property.

        *method* a callable that was registered with
        :meth:`add_observing_method` or :meth:`observes`.

        :rtype: bool
        """
        return self.__CUST_OBS_ARGS[method]

    pass # end of class

<commit_msg>BUG FIX due to typo.<commit_after>#  -------------------------------------------------------------------------
#  Author: Roberto Cavada <roboogle@gmail.com>
#
#  Copyright (C) 2006 by Roberto Cavada
#
#  pygtkmvc is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
#
#  pygtkmvc is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
#  Boston, MA 02110, USA.
#
#  For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
#  or email to the author Roberto Cavada <roboogle@gmail.com>.
#  Please report bugs to <roboogle@gmail.com>.
#  -------------------------------------------------------------------------

from support import decorators, utils, log

import inspect
import types

@decorators.good_decorator_accepting_args
def observes(*args):
    """
    Decorate a method in an :class:`Observer` subclass as a notification.
    Takes one to many property names as strings. If any of them changes
    in a model we observe, the method is called. If multiple names were given
    the method will be passed the name of the property that changed.

       .. versionadded:: 1.99.0

       .. deprecated:: 1.99.1
          Use :meth:`Observer.observes` instead.
    """

    @decorators.good_decorator
    def _decorator(_notified):
        # marks the method with observed properties
        _set = getattr(_notified, Observer._CUST_OBS_, set())
        _set |=  set(args)
        setattr(_notified, Observer._CUST_OBS_, _set)
        return _notified

    log.logger.warning("Decorator observer.observers is deprecated:"
                       "use Observer.observes instead")
    return _decorator
# ----------------------------------------------------------------------

# private metaclass, used to force read-only class-level properties.
class __NTinfo_mc__ (type):
    NT_AUTO  = property(lambda cls: 0) # for internal use only
    NT_VALUE  = property(lambda cls: 1)
    NT_BEFORE = property(lambda cls: 2)
    NT_AFTER  = property(lambda cls: 4)
    NT_SIGNAL = property(lambda cls: 8)
    pass # end of metaclass

class NTInfo (dict):
    """Notification Type of observers' notification methods.

    This class is used:

    1. As class when defining notificaion methods in observers, as it
       contains the constants for notification types.

    2. As class instance as parameter when a notification methods is
       called in observers.

    Notification Type Constants
    ===========================

    Class `NTInfo` exports these notification type constants:

    NT_VALUE: For value-change notification types.
    NT_BEFORE: For before-method-call notification types.
    NT_AFTER: For after-method-call notification types.
    NT_SIGNAL: For signal-emit notification types.

    Instance content
    ================

    Instances of class `NTInfo` will be received as the last argument
    (`info`) of any notification method::

      def notification_method(self, model, name, info)

    NTInfo is a dictionary (with some particular behaviour added),
    containing some information which is independent on the
    notification type, and some other information wich depends on the
    notification type.

    Common to all types
    -------------------

    For all notification types, NTInfo contains:

      model : the model containing the observable property triggering
              the notification. model is also passed as first argument
              of the notification method.

      name : the name of the observable property triggering the
             notification. name is also passed as second argument of
             the notification method.

      type : The value identifying the type of the notification
             method. This is generally one value among NT_VALUE,
             NT_BEFORE, NT_AFTER and NT_SIGNAL, but more generally it
             can be also a bitwise combination of these values.

    There are further information depending on the specific
    notification type:

    For NT_VALUE
    ------------

      old : the value that the observable property had before being
            changed.

      new : the new value that the observable property has been
            changed to.

    For NT_BEFORE
    -------------

      instance : the object instance which the method that is being
                 called belongs to.

      method_name : the name of the method that is being called.

      args : tuple of the arguments of the method that is being called.

      kwargs: dictionary of the keyword arguments of the method that
              is being called.

    For NT_AFTER
    ------------

      instance : the object instance which the method that has been
                 called belongs to.

      method_name : the name of the method that has been called.

      args : tuple of the arguments of the method that has been called.

      kwargs: dictionary of the keyword arguments of the method that
              has been called.

      result : the value returned by the method which has been called.

    For NT_SIGNAL
    -------------

      arg : the argument which was optionally specified when invoking
            emit() on the signal observable property.

    Type queries
    ============

    As already told, the type carried by a NTInfo instance can be
    accessed through attribute `type`, to be matched type constants
    available in class `NTInfo`, possibly with bit masks.  However,
    class `NTInfo` provides also predicates to query for type, like
    methods `is_value`, `is_before`, etc.

       .. versionadded:: 1.99.1

    """

    __metaclass__ = __NTinfo_mc__

    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        # if not already set, sets the default type
        if 'type' not in self: self['type'] = NTInfo.NT_VALUE
        return

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError

    def is_value(self):
        return self['type'] & NTInfo.NT_VALUE != 0

    def is_before(self):
        return self['type'] & NTInfo.NT_BEFORE != 0

    def is_after(self):
        return self['type'] & NTInfo.NT_AFTER != 0

    def is_signal(self):
        return self['type'] & NTInfo.NT_SIGNAL != 0

    pass # end of class

# ----------------------------------------------------------------------
class Observer (object):
    """
    .. note::

       Most methods in this class are used internally by the framework.
       Do not override them in subclasses.
    """

    # these are internal
    _CUST_OBS_ = "__custom_observes__"

    @classmethod
    @decorators.good_decorator_accepting_args
    def observes(cls, *args, **kwargs):
        """
        Decorate a method as a notification. Takes an arbitrary number
        of property names as strings. If none are given, the name of
        the method is used. If a given property changes in a model we
        observe, the method is called.

        Keyword argument 'type' can be used to specify the type of the
        notification. Values for type are type constants in class
        `NTInfo`.  Values for `type` can be combined for declaring
        notifications which can receive notification of several
        types. If `type` is not specified, the default value is
        `NTInfo.NT_VALUE`.

           .. versionadded:: 1.99.1
        """

        @decorators.good_decorator
        def _decorator(_notified):
            # marks the method with observed properties
            _set = getattr(_notified, Observer._CUST_OBS_, set())
            _set |=  set(names)
            setattr(_notified, Observer._CUST_OBS_, _set)
            return _notified

        # this handle the case of empty args
        if 1 == len(args) and type(args[0]) is types.FunctionType:
            names = ()
            return _decorator(args[0])

        # this is the case with arguments
        names = args
        return _decorator
    # ----------------------------------------------------------------------

    def __init__(self, model=None, spurious=False):
        """
        *model* is passed to :meth:`observe_model` if given.

        *spurious* indicates interest to be notified even when
        the value hasn't changed, like for: ::

         model.prop = model.prop

        .. versionadded:: 1.2.0
           Before that observers had to filter out spurious
           notifications themselves, as if the default was `True`. With
           :class:`~gtkmvc.observable.Signal` support this is no longer
           necessary.
        """

        self.__accepts_spurious__ = spurious

        # NOTE: In rev. 202 these maps were unified into
        #   __CUST_OBS_MAP only (the map contained pairs (method,
        #   args). However, this broke backward compatibility of code
        #   accessing the map through
        #   get_observing_methods. Now the informatio is split
        #   and the original information restored. To access the
        #   additional information (number of additional arguments
        #   required by observing methods) use the newly added methods.

        # Private maps: do not change/access them directly, use
        # methods to access them:
        self.__CUST_OBS_MAP = {} # prop name --> set of observing methods
        self.__CUST_OBS_ARGS = {} # observing method --> flag

        processed_props = set() # tracks already processed properties

        # searches all custom observer methods
        for cls in inspect.getmro(type(self)):
            # list of (method-name, method-object, list-of-properties-the-method-observes)
            meths = [ (name, meth, getattr(meth, Observer._CUST_OBS_))
                      for name, meth in cls.__dict__.iteritems()
                      if inspect.isfunction(meth) and hasattr(meth, Observer._CUST_OBS_)]

            # props processed in this class. This is used to avoid
            # processing the same props in base classes.
            cls_processed_props = set()

            # TODO: fix this code to call add_observing_method

            # since this is traversed top-bottom in the mro, the
            # first found match is the one to care
            for name, meth, pnames in meths:
                if 0 == len(pnames):
                    # no args were specified in the decorator. In this
                    # case the method does not take the prop name among
                    # its arguments, and the property name is taken
                    # from the method name.
                    if name not in processed_props:
                        if not self.__CUST_OBS_MAP.has_key(name):
                            self.__CUST_OBS_MAP[name] = set()
                            pass
                        _me = getattr(self, name)
                        self.__CUST_OBS_MAP[name].add(_me)

                        # first hit matters
                        if not self.__CUST_OBS_ARGS.has_key(_me):
                            self.__CUST_OBS_ARGS[_me] = False # no additional args
                            pass
                        cls_processed_props.add(name)
                        pass
                    pass
                else:
                    takes_arg = 1 < len(pnames)
                    for prop_name in pnames:
                        if prop_name not in processed_props:
                            if not self.__CUST_OBS_MAP.has_key(prop_name):
                                self.__CUST_OBS_MAP[prop_name] = set()
                                pass
                            _me = getattr(self, name)
                            self.__CUST_OBS_MAP[prop_name].add(_me)

                            # first hit matters
                            if not self.__CUST_OBS_ARGS.has_key(_me):
                                self.__CUST_OBS_ARGS[_me] = takes_arg
                                pass

                            cls_processed_props.add(prop_name)
                            pass
                        pass
                    pass
                pass

            # accumulates props processed in this class
            processed_props |= cls_processed_props
            pass # end of loop over classes in the mro

        if model: self.observe_model(model)
        return

    def observe_model(self, model):
        """
        Call :meth:`~gtkmvc.model.Model.register_observer` on *model*.
        """
        return model.register_observer(self)

    def relieve_model(self, model):
        """
        Call :meth:`~gtkmvc.model.Model.unregister_observer` on *model*.
        """
        return model.unregister_observer(self)

    def accepts_spurious_change(self):
        """
        Are we interested in spurious notifications?

        :rtype: bool
        """
        return self.__accepts_spurious__

    def add_observing_method(self, method,
                             prop_name_or_names):
        """
        Add notifications at runtime.

        *method* is a callable bound to `self`.

        *prop_name_or_names* can be a string or a sequence of strings.
        In the latter case the name of the property that changed will be
        passed to the method during notifications.

        Multiple calls may add different methods for the same names as well as
        additional names for the same method. However, the signature of the
        method is always determined by the last call. ::

         def observes_one_property(self, model, old, new)

         def observes_multiple(self, model, prop_name, old, new)

        .. note::

           Dynamic methods only work if you add/remove them before starting
           to observe a model.
        """

        if isinstance(prop_name_or_names, types.StringType):
            takes_name = False
            prop_names = (prop_name_or_names,)
        else:
            try:
                iter(prop_name_or_names)
            except TypeError:
                raise AssertionError("prop_name_or_names must be either a"
                    " string or an iterable")
            takes_name = True
            prop_names = prop_name_or_names
            pass

        for prop_name in prop_names:
            if not self.__CUST_OBS_MAP.has_key(prop_name):
                self.__CUST_OBS_MAP[prop_name] = set()
                pass
            self.__CUST_OBS_MAP[prop_name].add(method)
            pass
        self.__CUST_OBS_ARGS[method] = takes_name
        return

    def remove_observing_method(self, method, prop_names):
        """
        Remove dynamic notifications.

        *method* a callable that was registered with
        :meth:`add_observing_method` or :meth:`observes`.

        *prop_names* a sequence of strings. This need not correspond to any
        one `add` call.

        .. note::

           This can revert the effects of a decorator at runtime. Don't.
        """
        for prop_name in prop_names:
            _set = self.__CUST_OBS_MAP.get(prop_name, set())
            if method in _set: _set.remove(method)
            pass

        if method in self.__CUST_OBS_ARGS: del self.__CUST_OBS_ARGS[method]
        return

    def is_observing_method(self, method):
        """
        Returns `True` if the given method was previously added as an
        observing method, either dynamically or via decorator.
        """
        return self.__CUST_OBS_ARGS.has_key(method)

    def get_observing_methods(self, prop_name):
        """
        Return a possibly empty set of callables registered with
        :meth:`add_observing_method` or :meth:`observes` for *prop_name*.

        .. versionadded:: 1.99.1
           Replaces :meth:`get_custom_observing_methods`.
        """
        return self.__CUST_OBS_MAP.get(prop_name, set())

    # this is done to keep backward compatibility
    get_custom_observing_methods = get_observing_methods

    def does_observing_method_receive_prop_name(self, method):
        """
        Will the method be passed the name of the property that changed on
        notification? This additional argument is meant for methods that are
        registered for more than one property.

        *method* a callable that was registered with
        :meth:`add_observing_method` or :meth:`observes`.

        :rtype: bool
        """
        return self.__CUST_OBS_ARGS[method]

    pass # end of class

<|endoftext|>


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

"""
Created on 5 Dec 2016

@author: Bruno Beloff (bruno.beloff@southcoastscience.com)

DESCRIPTION
The particulates_sampler utility reads a set of values from the Alphasense optical particle counter (OPC). The reported
values are:

* per - the period of time between the previous reading and this reading
* pm1, pm2p5, pm10 - particulate densities of PM1, PM2.5 and PM10 in ug/m3
* bins - the particle count, for particles of increasing size
* mtf1, mtf3, mtf5, mtf7 - time taken for particle movement between system points
* sht.hmd, sht.tmp - humidity and temperature at point of sampling (OPC-N3 only)

The particulates_sampler utility operates by launching a background process. This OPCMonitor process reads the OPC
values at specified intervals (which may be different from the intervals used by the parent process). In addition,
the process power cycles the OPC if its laser safety control has tripped.

The scs_mfr/opc_conf utility is used to specify the OPC model, background process sampling time, and OPC control
power saving mode.

When the particulates_sampler utility starts, it power cycles the OPC. When the utility stops, it stops operations
on the OPC, and stops the OPC fan.

The particulates_sampler writes its output to stdout. As for all sensing utilities, the output format is a JSON
document with fields for:

* tag - the unique tag of the device (if the system ID is set)
* src - a source identifier ("N2" or "N3")
* rec - the recording date / time in ISO 8601 format
* val - a value field containing the sensed values
* exg - data interpretations, as specified by opc_conf.py

Command-line options allow for single-shot reading, multiple readings with specified time intervals, or readings
controlled by an independent scheduling process via a Unix semaphore.

SYNOPSIS
particulates_sampler.py [-f FILE] [{ -s SEMAPHORE | -i INTERVAL [-n SAMPLES] }] [-v]

EXAMPLES
./particulates_sampler.py -v -f /home/pi/SCS/conf/opc_conf_cs1.json

FILES
~/SCS/conf/opc_conf.json
~/SCS/conf/schedule.json
~/SCS/conf/system_id.json

DOCUMENT EXAMPLE (OPC-N3):
{"tag": "scs-be2-3", "src": "N3", "rec": "2019-12-10T15:24:04Z",
"val": {"per": 4.9, "pm1": 5.6, "pm2p5": 6.7, "pm10": 6.8,
"bin": [338, 42, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"mtf1": 83, "mtf3": 101, "mtf5": 0, "mtf7": 0, "sfr": 0.61,
"sht": {"hmd": 32.1, "tmp": 30.7}},
"exg": {"iseceen2v1": {"pm1": 3.9, "pm2p5": 4.2, "pm10": 4.8},
"isecsen2v2": {"pm1": 4.7, "pm2p5": 5.1, "pm10": 6.1}}}

SEE ALSO
scs_dev/interface_power
scs_dev/opc_cleaner
scs_dev/scheduler

scs_mfr/opc_cleaning_interval
scs_mfr/opc_conf
scs_mfr/opc_firmware_conf
scs_mfr/opc_version
scs_mfr/schedule
scs_mfr/system_id

BUGS
Because readings are time-sensitive, the particulates_sampler utility is not process-safe - it should therefore be run
by only one process at a time.

RESOURCES
https://en.wikipedia.org/wiki/ISO_8601
"""

import sys
import time

from scs_core.data.datetime import LocalizedDatetime
from scs_core.data.json import JSONify

try:
    from scs_exegesis.particulate.exegete_collection import ExegeteCollection
except ImportError:
    from scs_core.exegesis.particulate.exegete_collection import ExegeteCollection

from scs_core.exegesis.particulate.text import Text

from scs_core.sync.schedule import Schedule
from scs_core.sync.timed_runner import TimedRunner

from scs_core.sys.signalled_exit import SignalledExit
from scs_core.sys.system_id import SystemID

from scs_dev.cmd.cmd_sampler import CmdSampler
from scs_dev.sampler.particulates_sampler import ParticulatesSampler

from scs_dfe.climate.sht_conf import SHTConf
from scs_dfe.interface.interface_conf import InterfaceConf
from scs_dfe.particulate.opc_conf import OPCConf

from scs_host.bus.i2c import I2C
from scs_host.sync.schedule_runner import ScheduleRunner
from scs_host.sys.host import Host

# --------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':

    opc = None
    sht = None
    sampler = None

    # ----------------------------------------------------------------------------------------------------------------
    # cmd...

    cmd = CmdSampler()

    if cmd.verbose:
        print("particulates_sampler: %s" % cmd, file=sys.stderr)

    try:
        # ------------------------------------------------------------------------------------------------------------
        # resources...

        # Schedule...
        schedule = Schedule.load(Host)

        # SystemID...
        system_id = SystemID.load(Host)

        tag = None if system_id is None else system_id.message_tag()

        if system_id and cmd.verbose:
            print("particulates_sampler: %s" % system_id, file=sys.stderr)

        # OPCConf...
        opc_conf = OPCConf.load_from_file(cmd.file) if cmd.file else OPCConf.load(Host)

        if opc_conf is None:
            print("particulates_sampler: OPCConf not available.", file=sys.stderr)
            exit(1)

        if 0 < cmd.interval < opc_conf.sample_period:
            print("particulates_sampler: interval (%d) must not be less than opc_conf sample period (%d)." %
                  (cmd.interval, opc_conf.sample_period), file=sys.stderr)
            exit(1)

        # I2C...
        i2c_bus = Host.I2C_SENSORS if opc_conf.uses_spi() else opc_conf.bus
        I2C.open(i2c_bus)

        # Interface...
        interface_conf = InterfaceConf.load(Host)

        if interface_conf is None:
            print("particulates_sampler: InterfaceConf not available.", file=sys.stderr)
            exit(1)

        interface = interface_conf.interface()

        # exegetes...
        exegete_collection = ExegeteCollection.construct(opc_conf.exegete_names)

        for name in opc_conf.exegete_names:
            if not exegete_collection.has_member(name):
                print("particulates_sampler: WARNING: exegete '%s' is not available - ignoring." % name,
                      file=sys.stderr)
                sys.stderr.flush()

        # SHTConf...
        if exegete_collection.uses_external_sht():
            sht_conf = SHTConf.load(Host)

            if sht_conf is None:
                print("particulates_sampler: SHTConf not available.", file=sys.stderr)
                exit(1)

            # SHT...
            sht = sht_conf.ext_sht()

            if cmd.verbose:
                print("particulates_sampler: %s" % sht, file=sys.stderr)

        # OPCMonitor...
        opc_monitor = opc_conf.opc_monitor(interface, Host)

        # runner...
        runner = TimedRunner(cmd.interval, cmd.samples) if cmd.semaphore is None \
            else ScheduleRunner(cmd.semaphore)

        # sampler...
        sampler = ParticulatesSampler(runner, tag, opc_monitor)

        if cmd.verbose:
            print("particulates_sampler: %s" % sampler, file=sys.stderr)
            sys.stderr.flush()

        # ------------------------------------------------------------------------------------------------------------
        # check...

        if cmd.semaphore and (schedule is None or not schedule.contains(cmd.semaphore)):
            interface.power_opc(True)
            opc_monitor.operations_off()            # display may need the power to remain on

            if cmd.verbose:
                print("particulates_sampler: no schedule - stalled", file=sys.stderr)
                sys.stderr.flush()

            while True:
                time.sleep(1.0)

        # ------------------------------------------------------------------------------------------------------------
        # run...

        print("particulates_sampler: started run", file=sys.stderr)
        sys.stderr.flush()

        # signal handler...
        SignalledExit.construct("particulates_sampler", cmd.verbose)

        sampler.start()

        for opc_sample in sampler.samples():
            if opc_sample is None:
                continue

            # data interpretation...
            if exegete_collection.has_members():
                internal_sht_sample = opc_sample.values.get('sht')

                try:
                    external_sht_sample = None if sht is None else sht.sample()

                except OSError as ex:
                    external_sht_sample = None
                    print("particulates_sampler: %s" % ex, file=sys.stderr)
                    sys.stderr.flush()

                text = Text.construct_from_jdict(opc_sample.values)
                opc_sample.exegeses = exegete_collection.interpretation(text, internal_sht_sample, external_sht_sample)

            if cmd.verbose:
                now = LocalizedDatetime.now().utc()
                print("%s: particulates: %s" % (now.as_time(), opc_sample.rec.as_time()), file=sys.stderr)
                sys.stderr.flush()

            print(JSONify.dumps(opc_sample))
            sys.stdout.flush()

    # ----------------------------------------------------------------------------------------------------------------
    # end...

    except ConnectionError as ex:
        print("particulates_sampler: %s" % ex, file=sys.stderr)

    except (KeyboardInterrupt, SystemExit):
        pass

    finally:
        if cmd and cmd.verbose:
            print("particulates_sampler: finishing", file=sys.stderr)

        if sampler:
            sampler.stop()

        I2C.close()
<commit_msg>Added TODO<commit_after>#!/usr/bin/env python3

"""
Created on 5 Dec 2016

@author: Bruno Beloff (bruno.beloff@southcoastscience.com)

DESCRIPTION
The particulates_sampler utility reads a set of values from the Alphasense optical particle counter (OPC). The reported
values are:

* per - the period of time between the previous reading and this reading
* pm1, pm2p5, pm10 - particulate densities of PM1, PM2.5 and PM10 in ug/m3
* bins - the particle count, for particles of increasing size
* mtf1, mtf3, mtf5, mtf7 - time taken for particle movement between system points
* sht.hmd, sht.tmp - humidity and temperature at point of sampling (OPC-N3 only)

The particulates_sampler utility operates by launching a background process. This OPCMonitor process reads the OPC
values at specified intervals (which may be different from the intervals used by the parent process). In addition,
the process power cycles the OPC if its laser safety control has tripped.

The scs_mfr/opc_conf utility is used to specify the OPC model, background process sampling time, and OPC control
power saving mode.

When the particulates_sampler utility starts, it power cycles the OPC. When the utility stops, it stops operations
on the OPC, and stops the OPC fan.

The particulates_sampler writes its output to stdout. As for all sensing utilities, the output format is a JSON
document with fields for:

* tag - the unique tag of the device (if the system ID is set)
* src - a source identifier ("N2" or "N3")
* rec - the recording date / time in ISO 8601 format
* val - a value field containing the sensed values
* exg - data interpretations, as specified by opc_conf.py

Command-line options allow for single-shot reading, multiple readings with specified time intervals, or readings
controlled by an independent scheduling process via a Unix semaphore.

SYNOPSIS
particulates_sampler.py [-f FILE] [{ -s SEMAPHORE | -i INTERVAL [-n SAMPLES] }] [-v]

EXAMPLES
./particulates_sampler.py -v -f /home/pi/SCS/conf/opc_conf_cs1.json

FILES
~/SCS/conf/opc_conf.json
~/SCS/conf/schedule.json
~/SCS/conf/system_id.json

DOCUMENT EXAMPLE (OPC-N3):
{"tag": "scs-be2-3", "src": "N3", "rec": "2019-12-10T15:24:04Z",
"val": {"per": 4.9, "pm1": 5.6, "pm2p5": 6.7, "pm10": 6.8,
"bin": [338, 42, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"mtf1": 83, "mtf3": 101, "mtf5": 0, "mtf7": 0, "sfr": 0.61,
"sht": {"hmd": 32.1, "tmp": 30.7}},
"exg": {"iseceen2v1": {"pm1": 3.9, "pm2p5": 4.2, "pm10": 4.8},
"isecsen2v2": {"pm1": 4.7, "pm2p5": 5.1, "pm10": 6.1}}}

SEE ALSO
scs_dev/interface_power
scs_dev/opc_cleaner
scs_dev/scheduler

scs_mfr/opc_cleaning_interval
scs_mfr/opc_conf
scs_mfr/opc_firmware_conf
scs_mfr/opc_version
scs_mfr/schedule
scs_mfr/system_id

BUGS
Because readings are time-sensitive, the particulates_sampler utility is not process-safe - it should therefore be run
by only one process at a time.

RESOURCES
https://en.wikipedia.org/wiki/ISO_8601
"""

import sys
import time

from scs_core.data.datetime import LocalizedDatetime
from scs_core.data.json import JSONify

try:
    from scs_exegesis.particulate.exegete_collection import ExegeteCollection
except ImportError:
    from scs_core.exegesis.particulate.exegete_collection import ExegeteCollection

from scs_core.exegesis.particulate.text import Text

from scs_core.sync.schedule import Schedule
from scs_core.sync.timed_runner import TimedRunner

from scs_core.sys.signalled_exit import SignalledExit
from scs_core.sys.system_id import SystemID

from scs_dev.cmd.cmd_sampler import CmdSampler
from scs_dev.sampler.particulates_sampler import ParticulatesSampler

from scs_dfe.climate.sht_conf import SHTConf
from scs_dfe.interface.interface_conf import InterfaceConf
from scs_dfe.particulate.opc_conf import OPCConf

from scs_host.bus.i2c import I2C
from scs_host.sync.schedule_runner import ScheduleRunner
from scs_host.sys.host import Host

# --------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':

    opc = None
    sht = None
    sampler = None

    # ----------------------------------------------------------------------------------------------------------------
    # cmd...

    cmd = CmdSampler()

    if cmd.verbose:
        print("particulates_sampler: %s" % cmd, file=sys.stderr)

    try:
        # ------------------------------------------------------------------------------------------------------------
        # resources...

        # Schedule...
        schedule = Schedule.load(Host)

        # SystemID...
        system_id = SystemID.load(Host)

        tag = None if system_id is None else system_id.message_tag()

        if system_id and cmd.verbose:
            print("particulates_sampler: %s" % system_id, file=sys.stderr)

        # OPCConf...
        opc_conf = OPCConf.load_from_file(cmd.file) if cmd.file else OPCConf.load(Host)

        if opc_conf is None:
            print("particulates_sampler: OPCConf not available.", file=sys.stderr)
            exit(1)

        if 0 < cmd.interval < opc_conf.sample_period:
            print("particulates_sampler: interval (%d) must not be less than opc_conf sample period (%d)." %
                  (cmd.interval, opc_conf.sample_period), file=sys.stderr)
            exit(1)

        # I2C...
        i2c_bus = Host.I2C_SENSORS if opc_conf.uses_spi() else opc_conf.bus
        I2C.open(i2c_bus)

        # Interface...
        interface_conf = InterfaceConf.load(Host)

        if interface_conf is None:
            print("particulates_sampler: InterfaceConf not available.", file=sys.stderr)
            exit(1)

        interface = interface_conf.interface()

        # exegetes...
        exegete_collection = ExegeteCollection.construct(opc_conf.exegete_names)

        for name in opc_conf.exegete_names:
            if not exegete_collection.has_member(name):
                print("particulates_sampler: WARNING: exegete '%s' is not available - ignoring." % name,
                      file=sys.stderr)
                sys.stderr.flush()

        # SHTConf...
        if exegete_collection.uses_external_sht():
            sht_conf = SHTConf.load(Host)

            if sht_conf is None:
                print("particulates_sampler: SHTConf not available.", file=sys.stderr)
                exit(1)

            # SHT...
            sht = sht_conf.ext_sht()

            if cmd.verbose:
                print("particulates_sampler: %s" % sht, file=sys.stderr)

        # OPCMonitor...
        opc_monitor = opc_conf.opc_monitor(interface, Host)

        # runner...
        runner = TimedRunner(cmd.interval, cmd.samples) if cmd.semaphore is None \
            else ScheduleRunner(cmd.semaphore)

        # sampler...
        sampler = ParticulatesSampler(runner, tag, opc_monitor)

        if cmd.verbose:
            print("particulates_sampler: %s" % sampler, file=sys.stderr)
            sys.stderr.flush()

        # ------------------------------------------------------------------------------------------------------------
        # check...

        if cmd.semaphore and (schedule is None or not schedule.contains(cmd.semaphore)):
            interface.power_opc(True)
            opc_monitor.operations_off()            # display may need the SPI power to remain on

            if cmd.verbose:
                print("particulates_sampler: no schedule - stalled", file=sys.stderr)
                sys.stderr.flush()

            while True:
                time.sleep(1.0)

        # ------------------------------------------------------------------------------------------------------------
        # run...

        print("particulates_sampler: started run", file=sys.stderr)
        sys.stderr.flush()

        # signal handler...
        SignalledExit.construct("particulates_sampler", cmd.verbose)

        sampler.start()

        for opc_sample in sampler.samples():
            if opc_sample is None:
                continue

            # data interpretation...
            if exegete_collection.has_members():
                internal_sht_sample = opc_sample.values.get('sht')

                try:
                    external_sht_sample = None if sht is None else sht.sample()

                except OSError as ex:
                    external_sht_sample = None
                    print("particulates_sampler: %s" % ex, file=sys.stderr)
                    sys.stderr.flush()

                text = Text.construct_from_jdict(opc_sample.values)
                opc_sample.exegeses = exegete_collection.interpretation(text, internal_sht_sample, external_sht_sample)

            if cmd.verbose:
                now = LocalizedDatetime.now().utc()
                print("%s: particulates: %s" % (now.as_time(), opc_sample.rec.as_time()), file=sys.stderr)
                sys.stderr.flush()

            print(JSONify.dumps(opc_sample))
            sys.stdout.flush()

    # ----------------------------------------------------------------------------------------------------------------
    # end...

    except ConnectionError as ex:
        print("particulates_sampler: %s" % ex, file=sys.stderr)

    except (KeyboardInterrupt, SystemExit):
        pass

    finally:
        if cmd and cmd.verbose:
            print("particulates_sampler: finishing", file=sys.stderr)

        if sampler:
            sampler.stop()

        I2C.close()
<|endoftext|>


<commit_before>import os
import unittest
from subprocess import Popen, PIPE
from tempfile import mkstemp

class TestFixPEP8Error(unittest.TestCase):

    def setUp(self):
        self.tempfile = mkstemp()

    def tearDown(self):
        os.remove(self.tempfile[1])

    def _inner_setup(self, line):
        f = open(self.tempfile[1], 'w')
        f.write(line)
        f.close()
        root_dir = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0]
        p = Popen([os.path.join(root_dir, 'autopep8.py'),
                   self.tempfile[1]], stdout=PIPE)
        self.result = p.stdout.read()

    def test_e111_short(self):
        line = "class Dummy:\n  def __init__(self):\n    pass\n"
        fixed = "class Dummy:\n    def __init__(self):\n        pass\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e111_long(self):
        line = "class Dummy:\n     def __init__(self):\n          pass\n"
        fixed = "class Dummy:\n    def __init__(self):\n        pass\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e221(self):
        line = "a = 1  + 1\n"
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e222(self):
        line = "a = 1 +  1\n"
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e223(self):
        line = "a = 1	+ 1\n"  # include TAB
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e223_double(self):
        line = "a = 1		+ 1\n"  # include TAB
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e224(self):
        line = "a = 11 +	1\n"    # include TAB
        fixed = "a = 11 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e224_double(self):
        line = "a = 11 +		1\n"    # include TAB
        fixed = "a = 11 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e261(self):
        line = "print 'a b '# comment\n"
        fixed = "print 'a b '  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e261_with_dictionary(self):
        line = "d = {# comment\n1: 2}\n"
        fixed = "d = {  # comment\n1: 2}\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e303(self):
        line = "\n\n\n# alpha\n\n1\n"
        fixed = "\n\n# alpha\n1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e401(self):
        line = "import os, sys\n"
        fixed = "import os\nimport sys\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e401_with_indentation(self):
        line = "def a():\n    import os, sys\n"
        fixed = "def a():\n    import os\n    import sys\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

class TestFixPEP8Warn(unittest.TestCase):

    def setUp(self):
        self.tempfile = mkstemp()

    def tearDown(self):
        os.remove(self.tempfile[1])

    def _inner_setup(self, line):
        f = open(self.tempfile[1], 'w')
        f.write(line)
        f.close()
        root_dir = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0]
        p = Popen([os.path.join(root_dir, 'autopep8.py'),
                   self.tempfile[1]], stdout=PIPE)
        self.result = p.stdout.read()

    def test_w291(self):
        line = "print 'a b '\t \n"
        fixed = "print 'a b '\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w291_with_comment(self):
        line = "print 'a b '  # comment\t \n"
        fixed = "print 'a b '  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w292(self):
        line = "1\n2"
        fixed = "1\n2\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_string(self):
        line = "raise ValueError, \"w602 test\"\n"
        fixed = "raise ValueError(\"w602 test\")\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_string_with_comment(self):
        line = "raise ValueError, \"w602 test\"  # comment\n"
        fixed = "raise ValueError(\"w602 test\")  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_tuple(self):
        line = "raise ValueError, ('a', 'b')\n"
        fixed = "raise ValueError('a', 'b')\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

if __name__ == '__main__':
    unittest.main()
<commit_msg>Add test case for E111 bug<commit_after>import os
import unittest
from subprocess import Popen, PIPE
from tempfile import mkstemp

class TestFixPEP8Error(unittest.TestCase):

    def setUp(self):
        self.tempfile = mkstemp()

    def tearDown(self):
        os.remove(self.tempfile[1])

    def _inner_setup(self, line):
        f = open(self.tempfile[1], 'w')
        f.write(line)
        f.close()
        root_dir = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0]
        p = Popen([os.path.join(root_dir, 'autopep8.py'),
                   self.tempfile[1]], stdout=PIPE)
        self.result = p.stdout.read()

    def test_e111_short(self):
        line = "class Dummy:\n  def __init__(self):\n    pass\n"
        fixed = "class Dummy:\n    def __init__(self):\n        pass\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e111_long(self):
        line = "class Dummy:\n     def __init__(self):\n          pass\n"
        fixed = "class Dummy:\n    def __init__(self):\n        pass\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e111_bug(self):
        line = """
while True:
    if True:
       1
"""
        fixed = """
while True:
    if True:
        1
"""
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e221(self):
        line = "a = 1  + 1\n"
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e222(self):
        line = "a = 1 +  1\n"
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e223(self):
        line = "a = 1	+ 1\n"  # include TAB
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e223_double(self):
        line = "a = 1		+ 1\n"  # include TAB
        fixed = "a = 1 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e224(self):
        line = "a = 11 +	1\n"    # include TAB
        fixed = "a = 11 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e224_double(self):
        line = "a = 11 +		1\n"    # include TAB
        fixed = "a = 11 + 1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e261(self):
        line = "print 'a b '# comment\n"
        fixed = "print 'a b '  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e261_with_dictionary(self):
        line = "d = {# comment\n1: 2}\n"
        fixed = "d = {  # comment\n1: 2}\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e303(self):
        line = "\n\n\n# alpha\n\n1\n"
        fixed = "\n\n# alpha\n1\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e401(self):
        line = "import os, sys\n"
        fixed = "import os\nimport sys\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_e401_with_indentation(self):
        line = "def a():\n    import os, sys\n"
        fixed = "def a():\n    import os\n    import sys\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

class TestFixPEP8Warn(unittest.TestCase):

    def setUp(self):
        self.tempfile = mkstemp()

    def tearDown(self):
        os.remove(self.tempfile[1])

    def _inner_setup(self, line):
        f = open(self.tempfile[1], 'w')
        f.write(line)
        f.close()
        root_dir = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0]
        p = Popen([os.path.join(root_dir, 'autopep8.py'),
                   self.tempfile[1]], stdout=PIPE)
        self.result = p.stdout.read()

    def test_w291(self):
        line = "print 'a b '\t \n"
        fixed = "print 'a b '\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w291_with_comment(self):
        line = "print 'a b '  # comment\t \n"
        fixed = "print 'a b '  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w292(self):
        line = "1\n2"
        fixed = "1\n2\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_string(self):
        line = "raise ValueError, \"w602 test\"\n"
        fixed = "raise ValueError(\"w602 test\")\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_string_with_comment(self):
        line = "raise ValueError, \"w602 test\"  # comment\n"
        fixed = "raise ValueError(\"w602 test\")  # comment\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

    def test_w602_arg_is_tuple(self):
        line = "raise ValueError, ('a', 'b')\n"
        fixed = "raise ValueError('a', 'b')\n"
        self._inner_setup(line)
        self.assertEqual(self.result, fixed)

if __name__ == '__main__':
    unittest.main()
<|endoftext|>