<commit_before># Copyright 2016 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import re

import models

XREF_RE = re.compile(r'k8s-gubernator.appspot.com/build(/[^])\s]+/\d+)')

class Deduper(object):
    ''' A memory-saving string deduplicator for Python datastructures.

    This is somewhat like the built-in intern() function, but without pinning memory
    permanently.

    Tries to reduce memory usage by making equivalent strings point at the same object.
    This reduces memory usage for large, repetitive JSON structures by >2x.
    '''

    def __init__(self):
        self.strings = {}

    def dedup(self, obj):
        if isinstance(obj, basestring):
            return self.strings.setdefault(obj, obj)
        elif isinstance(obj, dict):
            return {self.dedup(k): self.dedup(v) for k, v in obj.iteritems()}
        elif isinstance(obj, tuple):
            return tuple(self.dedup(x) for x in obj)
        elif isinstance(obj, list):
            return [self.dedup(x) for x in obj]
        return obj

def classify_issue(repo, number):
    '''
    Classify an issue in a repo based on events in Datastore.

    Args:
        repo: string
        number: int
    Returns:
        is_pr: bool
        is_open: bool
        involved: list of strings representing usernames involved
        payload: a dict, see full description for classify below.
        last_event_timestamp: the timestamp of the most recent event.
    '''
    ancestor = models.GithubResource.make_key(repo, number)
    logging.debug('finding webhooks for %s %s', repo, number)
    events = list(models.GithubWebhookRaw.query(ancestor=ancestor))
    events.sort(key=lambda e: e.timestamp)
    logging.debug('classifying %s %s (%d events)', repo, number, len(events))
    deduper = Deduper()
    last_event_timestamp = events[-1].timestamp

    event_tuples = [deduper.dedup(event.to_tuple()) for event in events]

    del deduper  # attempt to save memory
    del events

    merged = get_merged(event_tuples)
    statuses = None
    if 'head' in merged:
        statuses = {}
        for status in models.GHStatus.query_for_sha(repo, merged['head']['sha']):
            last_event_timestamp = max(last_event_timestamp, status.updated_at)
            statuses[status.context] = [
                status.state, status.target_url, status.description]

    return list(classify(event_tuples, statuses)) + [last_event_timestamp]

def get_merged(events):
    '''
    Determine the most up-to-date view of the issue given its inclusion
    in a series of events.

    Note that different events have different levels of detail-- comments
    don't include head SHA information, pull request events don't have label
    information, etc.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        body: a dict representing the issue's latest state.
    '''
    merged = {}
    for _event, body, _timestamp in events:
        if 'issue' in body:
            merged.update(body['issue'])
        if 'pull_request' in body:
            merged.update(body['pull_request'])
    return merged

def get_labels(events):
    '''
    Determine the labels applied to an issue.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        labels: the currently applied labels as {label_name: label_color}
    '''
    labels = []
    for event, body, _timestamp in events:
        if 'issue' in body:
            # issues come with labels, so we can update here
            labels = body['issue']['labels']
        # pull_requests don't include their full labels :(
        action = body.get('action')
        if event == 'pull_request':
            # Pull request label events don't come with a full label set.
            # Track them explicitly here.
            try:
                if action in ('labeled', 'unlabeled') and 'label' not in body:
                    logging.warning('label event with no labels (multiple changes?)')
                elif action == 'labeled':
                    label = body['label']
                    if label not in labels:
                        labels.append(label)
                elif action == 'unlabeled':
                    label = body['label']
                    if label in labels:
                        labels.remove(label)
            except:
                logging.exception('??? %r', body)
                raise
    return {label['name']: label['color'] for label in labels}

def get_skip_comments(events, skip_users=None):
    '''
    Determine comment ids that should be ignored, either because of
        deletion or because the user should be skipped.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        comment_ids: a set of comment ids that were deleted or made by
            users that should be skiped.
    '''
    if skip_users is None:
        skip_users = []

    skip_comments = set()
    for event, body, _timestamp in events:
        action = body.get('action')
        if event in ('issue_comment', 'pull_request_review_comment'):
            comment_id = body['comment']['id']
            if action == 'deleted' or body['sender']['login'] in skip_users:
                skip_comments.add(comment_id)
    return skip_comments

def classify(events, statuses=None):
    '''
    Given an event-stream for an issue and status-getter, process
    the events and determine what action should be taken, if any.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        is_pr: bool
        is_open: bool
        involved: list of strings representing usernames involved
        payload: a dictionary of additional information, including:
            {
                'author': str author_name,
                'title': str issue title,
                'labels': {label_name: label_color},
                'attn': {user_name: reason},
                'mergeable': bool,
                'comments': [{'user': str name, 'comment': comment, 'timestamp': str iso8601}],
                'xrefs': list of builds referenced (by GCS path),
            }
    '''
    merged = get_merged(events)
    labels = get_labels(events)
    xrefs = get_xrefs(get_comments(events), merged)

    is_pr = 'head' in merged or 'pull_request' in merged
    is_open = merged['state'] != 'closed'
    author = merged['user']['login']
    assignees = sorted(assignee['login'] for assignee in merged['assignees'])
    involved = [author] + assignees

    payload = {
        'author': author,
        'assignees': assignees,
        'title': merged['title'],
        'labels': labels,
        'xrefs': xrefs,
    }

    if is_pr:
        if is_open:
            payload['needs_rebase'] = 'needs-rebase' in labels or merged.get('mergeable') == 'false'
        payload['additions'] = merged.get('additions', 0)
        payload['deletions'] = merged.get('deletions', 0)
        if 'head' in merged:
            payload['head'] = merged['head']['sha']

    if statuses:
        payload['status'] = statuses

    payload['attn'] = calculate_attention(distill_events(events), payload)

    return is_pr, is_open, involved, payload

def get_xrefs(comments, merged):
    xrefs = set(XREF_RE.findall(merged.get('body') or ''))
    for c in comments:
        xrefs.update(XREF_RE.findall(c['comment']))
    return sorted(xrefs)

def get_comments(events):
    '''
    Pick comments and pull-request review comments out of a list of events.
    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        comments: a list of dict(author=..., comment=..., timestamp=...),
                  ordered with the earliest comment first.
    '''
    comments = {}  # comment_id : comment
    for event, body, _timestamp in events:
        action = body.get('action')
        if event in ('issue_comment', 'pull_request_review_comment'):
            comment_id = body['comment']['id']
            if action == 'deleted':
                comments.pop(comment_id, None)
            else:
                comments[comment_id] = body['comment']
    return [
            {
                'author': c['user']['login'],
                'comment': c['body'],
                'timestamp': c['created_at']
            }
            for c in sorted(comments.values(), key=lambda c: c['created_at'])
    ]

def distill_events(events):
    '''
    Given a sequence of events, return a series of user-action tuples
    relevant to determining user state.
    '''
    bots = [
        'k8s-bot',
        'k8s-ci-robot',
        'k8s-oncall',
        'k8s-reviewable',
    ]
    skip_comments = get_skip_comments(events, bots)

    output = []
    for event, body, timestamp in events:
        action = body.get('action')
        user = body.get('sender', {}).get('login')
        if event in ('issue_comment', 'pull_request_review_comment'):
            if body['comment']['id'] in skip_comments:
                continue
            if action == 'created':
                output.append(('comment', user, timestamp))
        if event == 'pull_request':
            if action in ('opened', 'reopened', 'synchronize'):
                output.append(('push', user, timestamp))
            if action == 'labeled' and 'label' in body:
                output.append(('label ' + body['label']['name'].lower(), user, timestamp))
    return output

def evaluate_fsm(events, start, transitions):
    '''
    Given a series of event tuples and a start state, execute the list of transitions
    and return the resulting state, the time it entered that state, and the last time
    the state would be entered (self-transitions are allowed).

    transitions is a list of tuples
    (state_before str, state_after str, condition str or callable)

    The transition occurs if condition equals the action (as a str), or if
    condition(action, user) is True.
    '''
    state = start
    state_start = 0 # time that we entered this state
    state_last = 0  # time of last transition into this state
    for action, user, timestamp in events:
        for state_before, state_after, condition in transitions:
            if state_before is None or state_before == state:
                if condition == action or (callable(condition) and condition(action, user)):
                    if state_after != state:
                        state_start = timestamp
                    state = state_after
                    state_last = timestamp
                    break
    return state, state_start, state_last

def get_author_state(author, distilled_events):
    '''
    Determine the state of the author given a series of distilled events.
    '''
    return evaluate_fsm(distilled_events, start='waiting', transitions=[
        # before, after, condition
        (None, 'address comments', lambda a, u: a == 'comment' and u != author),
        ('address comments', 'waiting', 'push'),
        ('address comments', 'waiting', lambda a, u: a == 'comment' and u == author),
    ])

def get_assignee_state(assignee, author, distilled_events):
    '''
    Determine the state of an assignee given a series of distilled events.
    '''
    return evaluate_fsm(distilled_events, start='needs review', transitions=[
        # before, after, condition
        ('needs review', 'waiting', lambda a, u: u == assignee and a in ('comment', 'label lgtm')),
        (None, 'needs review', 'push'),
        (None, 'needs review', lambda a, u: a == 'comment' and u == author),
    ])

def calculate_attention(distilled_events, payload):
    '''
    Given information about an issue, determine who should look at it.

    It can include start and last update time for various states --
    "address comments#123#456" means that something has been in 'address comments' since
    123, and there was some other event that put it in 'address comments' at 456.
    '''
    author = payload['author']
    assignees = payload['assignees']

    attn = {}
    def notify(to, reason):
        attn[to] = reason

    if any(state == 'failure' for state, _url, _desc
           in payload.get('status', {}).values()):
        notify(author, 'fix tests')

    for assignee in assignees:
        assignee_state, first, last = get_assignee_state(assignee, author, distilled_events)
        if assignee_state != 'waiting':
            notify(assignee, '%s#%s#%s' % (assignee_state, first, last))

    author_state, first, last = get_author_state(author, distilled_events)
    if author_state != 'waiting':
        notify(author, '%s#%s#%s' % (author_state, first, last))

    if payload.get('needs_rebase'):
        notify(author, 'needs rebase')
    if 'release-note-label-needed' in payload['labels']:
        notify(author, 'needs release-note label')

    return attn
<commit_msg>Add k8s-merge-robot to the list of bots in Gubernator.<commit_after># Copyright 2016 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import re

import models

XREF_RE = re.compile(r'k8s-gubernator.appspot.com/build(/[^])\s]+/\d+)')

class Deduper(object):
    ''' A memory-saving string deduplicator for Python datastructures.

    This is somewhat like the built-in intern() function, but without pinning memory
    permanently.

    Tries to reduce memory usage by making equivalent strings point at the same object.
    This reduces memory usage for large, repetitive JSON structures by >2x.
    '''

    def __init__(self):
        self.strings = {}

    def dedup(self, obj):
        if isinstance(obj, basestring):
            return self.strings.setdefault(obj, obj)
        elif isinstance(obj, dict):
            return {self.dedup(k): self.dedup(v) for k, v in obj.iteritems()}
        elif isinstance(obj, tuple):
            return tuple(self.dedup(x) for x in obj)
        elif isinstance(obj, list):
            return [self.dedup(x) for x in obj]
        return obj

def classify_issue(repo, number):
    '''
    Classify an issue in a repo based on events in Datastore.

    Args:
        repo: string
        number: int
    Returns:
        is_pr: bool
        is_open: bool
        involved: list of strings representing usernames involved
        payload: a dict, see full description for classify below.
        last_event_timestamp: the timestamp of the most recent event.
    '''
    ancestor = models.GithubResource.make_key(repo, number)
    logging.debug('finding webhooks for %s %s', repo, number)
    events = list(models.GithubWebhookRaw.query(ancestor=ancestor))
    events.sort(key=lambda e: e.timestamp)
    logging.debug('classifying %s %s (%d events)', repo, number, len(events))
    deduper = Deduper()
    last_event_timestamp = events[-1].timestamp

    event_tuples = [deduper.dedup(event.to_tuple()) for event in events]

    del deduper  # attempt to save memory
    del events

    merged = get_merged(event_tuples)
    statuses = None
    if 'head' in merged:
        statuses = {}
        for status in models.GHStatus.query_for_sha(repo, merged['head']['sha']):
            last_event_timestamp = max(last_event_timestamp, status.updated_at)
            statuses[status.context] = [
                status.state, status.target_url, status.description]

    return list(classify(event_tuples, statuses)) + [last_event_timestamp]

def get_merged(events):
    '''
    Determine the most up-to-date view of the issue given its inclusion
    in a series of events.

    Note that different events have different levels of detail-- comments
    don't include head SHA information, pull request events don't have label
    information, etc.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        body: a dict representing the issue's latest state.
    '''
    merged = {}
    for _event, body, _timestamp in events:
        if 'issue' in body:
            merged.update(body['issue'])
        if 'pull_request' in body:
            merged.update(body['pull_request'])
    return merged

def get_labels(events):
    '''
    Determine the labels applied to an issue.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        labels: the currently applied labels as {label_name: label_color}
    '''
    labels = []
    for event, body, _timestamp in events:
        if 'issue' in body:
            # issues come with labels, so we can update here
            labels = body['issue']['labels']
        # pull_requests don't include their full labels :(
        action = body.get('action')
        if event == 'pull_request':
            # Pull request label events don't come with a full label set.
            # Track them explicitly here.
            try:
                if action in ('labeled', 'unlabeled') and 'label' not in body:
                    logging.warning('label event with no labels (multiple changes?)')
                elif action == 'labeled':
                    label = body['label']
                    if label not in labels:
                        labels.append(label)
                elif action == 'unlabeled':
                    label = body['label']
                    if label in labels:
                        labels.remove(label)
            except:
                logging.exception('??? %r', body)
                raise
    return {label['name']: label['color'] for label in labels}

def get_skip_comments(events, skip_users=None):
    '''
    Determine comment ids that should be ignored, either because of
        deletion or because the user should be skipped.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        comment_ids: a set of comment ids that were deleted or made by
            users that should be skiped.
    '''
    if skip_users is None:
        skip_users = []

    skip_comments = set()
    for event, body, _timestamp in events:
        action = body.get('action')
        if event in ('issue_comment', 'pull_request_review_comment'):
            comment_id = body['comment']['id']
            if action == 'deleted' or body['sender']['login'] in skip_users:
                skip_comments.add(comment_id)
    return skip_comments

def classify(events, statuses=None):
    '''
    Given an event-stream for an issue and status-getter, process
    the events and determine what action should be taken, if any.

    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        is_pr: bool
        is_open: bool
        involved: list of strings representing usernames involved
        payload: a dictionary of additional information, including:
            {
                'author': str author_name,
                'title': str issue title,
                'labels': {label_name: label_color},
                'attn': {user_name: reason},
                'mergeable': bool,
                'comments': [{'user': str name, 'comment': comment, 'timestamp': str iso8601}],
                'xrefs': list of builds referenced (by GCS path),
            }
    '''
    merged = get_merged(events)
    labels = get_labels(events)
    xrefs = get_xrefs(get_comments(events), merged)

    is_pr = 'head' in merged or 'pull_request' in merged
    is_open = merged['state'] != 'closed'
    author = merged['user']['login']
    assignees = sorted(assignee['login'] for assignee in merged['assignees'])
    involved = [author] + assignees

    payload = {
        'author': author,
        'assignees': assignees,
        'title': merged['title'],
        'labels': labels,
        'xrefs': xrefs,
    }

    if is_pr:
        if is_open:
            payload['needs_rebase'] = 'needs-rebase' in labels or merged.get('mergeable') == 'false'
        payload['additions'] = merged.get('additions', 0)
        payload['deletions'] = merged.get('deletions', 0)
        if 'head' in merged:
            payload['head'] = merged['head']['sha']

    if statuses:
        payload['status'] = statuses

    payload['attn'] = calculate_attention(distill_events(events), payload)

    return is_pr, is_open, involved, payload

def get_xrefs(comments, merged):
    xrefs = set(XREF_RE.findall(merged.get('body') or ''))
    for c in comments:
        xrefs.update(XREF_RE.findall(c['comment']))
    return sorted(xrefs)

def get_comments(events):
    '''
    Pick comments and pull-request review comments out of a list of events.
    Args:
        events: a list of (event_type str, event_body dict, timestamp).
    Returns:
        comments: a list of dict(author=..., comment=..., timestamp=...),
                  ordered with the earliest comment first.
    '''
    comments = {}  # comment_id : comment
    for event, body, _timestamp in events:
        action = body.get('action')
        if event in ('issue_comment', 'pull_request_review_comment'):
            comment_id = body['comment']['id']
            if action == 'deleted':
                comments.pop(comment_id, None)
            else:
                comments[comment_id] = body['comment']
    return [
            {
                'author': c['user']['login'],
                'comment': c['body'],
                'timestamp': c['created_at']
            }
            for c in sorted(comments.values(), key=lambda c: c['created_at'])
    ]

def distill_events(events):
    '''
    Given a sequence of events, return a series of user-action tuples
    relevant to determining user state.
    '''
    bots = [
        'k8s-bot',
        'k8s-ci-robot',
        'k8s-merge-robot',
        'k8s-oncall',
        'k8s-reviewable',
    ]
    skip_comments = get_skip_comments(events, bots)

    output = []
    for event, body, timestamp in events:
        action = body.get('action')
        user = body.get('sender', {}).get('login')
        if event in ('issue_comment', 'pull_request_review_comment'):
            if body['comment']['id'] in skip_comments:
                continue
            if action == 'created':
                output.append(('comment', user, timestamp))
        if event == 'pull_request':
            if action in ('opened', 'reopened', 'synchronize'):
                output.append(('push', user, timestamp))
            if action == 'labeled' and 'label' in body:
                output.append(('label ' + body['label']['name'].lower(), user, timestamp))
    return output

def evaluate_fsm(events, start, transitions):
    '''
    Given a series of event tuples and a start state, execute the list of transitions
    and return the resulting state, the time it entered that state, and the last time
    the state would be entered (self-transitions are allowed).

    transitions is a list of tuples
    (state_before str, state_after str, condition str or callable)

    The transition occurs if condition equals the action (as a str), or if
    condition(action, user) is True.
    '''
    state = start
    state_start = 0 # time that we entered this state
    state_last = 0  # time of last transition into this state
    for action, user, timestamp in events:
        for state_before, state_after, condition in transitions:
            if state_before is None or state_before == state:
                if condition == action or (callable(condition) and condition(action, user)):
                    if state_after != state:
                        state_start = timestamp
                    state = state_after
                    state_last = timestamp
                    break
    return state, state_start, state_last

def get_author_state(author, distilled_events):
    '''
    Determine the state of the author given a series of distilled events.
    '''
    return evaluate_fsm(distilled_events, start='waiting', transitions=[
        # before, after, condition
        (None, 'address comments', lambda a, u: a == 'comment' and u != author),
        ('address comments', 'waiting', 'push'),
        ('address comments', 'waiting', lambda a, u: a == 'comment' and u == author),
    ])

def get_assignee_state(assignee, author, distilled_events):
    '''
    Determine the state of an assignee given a series of distilled events.
    '''
    return evaluate_fsm(distilled_events, start='needs review', transitions=[
        # before, after, condition
        ('needs review', 'waiting', lambda a, u: u == assignee and a in ('comment', 'label lgtm')),
        (None, 'needs review', 'push'),
        (None, 'needs review', lambda a, u: a == 'comment' and u == author),
    ])

def calculate_attention(distilled_events, payload):
    '''
    Given information about an issue, determine who should look at it.

    It can include start and last update time for various states --
    "address comments#123#456" means that something has been in 'address comments' since
    123, and there was some other event that put it in 'address comments' at 456.
    '''
    author = payload['author']
    assignees = payload['assignees']

    attn = {}
    def notify(to, reason):
        attn[to] = reason

    if any(state == 'failure' for state, _url, _desc
           in payload.get('status', {}).values()):
        notify(author, 'fix tests')

    for assignee in assignees:
        assignee_state, first, last = get_assignee_state(assignee, author, distilled_events)
        if assignee_state != 'waiting':
            notify(assignee, '%s#%s#%s' % (assignee_state, first, last))

    author_state, first, last = get_author_state(author, distilled_events)
    if author_state != 'waiting':
        notify(author, '%s#%s#%s' % (author_state, first, last))

    if payload.get('needs_rebase'):
        notify(author, 'needs rebase')
    if 'release-note-label-needed' in payload['labels']:
        notify(author, 'needs release-note label')

    return attn
<|endoftext|>


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

# Copyright 2011-2014, Nigel Small
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import division, unicode_literals

from py2neo.core import Node, Relationship, Path, PropertySet, LabelSet
from py2neo.util import compact
from py2neo.batch.core import Batch, PostJob, CypherJob, DeleteJob, PutJob

# TODO: find a better home for this method
def _create_query(p, unique):
    nodes, path, values, params = [], [], [], {}

    def append_node(i, node):
        if node is None:
            path.append("(n{0})".format(i))
            values.append("n{0}".format(i))
        elif node.bound:
            path.append("(n{0})".format(i))
            nodes.append("n{0}=node({{i{0}}})".format(i))
            params["i{0}".format(i)] = node._id
            values.append("n{0}".format(i))
        else:
            path.append("(n{0} {{p{0}}})".format(i))
            params["p{0}".format(i)] = node.properties
            values.append("n{0}".format(i))

    def append_rel(i, rel):
        if rel.properties:
            path.append("-[r{0}:`{1}` {{q{0}}}]->".format(i, rel.type))
            params["q{0}".format(i)] = compact(rel.properties)
            values.append("r{0}".format(i))
        else:
            path.append("-[r{0}:`{1}`]->".format(i, rel.type))
            values.append("r{0}".format(i))

    append_node(0, p.nodes[0])
    for i, rel in enumerate(p.rels):
        append_rel(i, rel)
        append_node(i + 1, p.nodes[i + 1])
    clauses = []
    if nodes:
        clauses.append("START {0}".format(",".join(nodes)))
    if unique:
        clauses.append("CREATE UNIQUE p={0}".format("".join(path)))
    else:
        clauses.append("CREATE p={0}".format("".join(path)))
    #clauses.append("RETURN {0}".format(",".join(values)))
    clauses.append("RETURN p")
    query = " ".join(clauses)
    return query, params

class CreateNodeJob(PostJob):

    def __init__(self, **properties):
        PostJob.__init__(self, "node", properties)

class CreateRelationshipJob(PostJob):

    def __init__(self, start_node, rel, end_node, **properties):
        uri = self.uri_for(start_node, "relationships")
        body = {"type": rel.type, "to": self.uri_for(end_node)}
        if rel.properties or properties:
            body["data"] = dict(rel.properties, **properties)
        PostJob.__init__(self, uri, body)

class CreatePathJob(CypherJob):

    def __init__(self, *entities):
        CypherJob.__init__(self, *_create_query(Path(*entities), unique=False))

class MergePathJob(CypherJob):

    def __init__(self, *entities):
        CypherJob.__init__(self, *_create_query(Path(*entities), unique=True))

class DeleteEntityJob(DeleteJob):

    def __init__(self, entity):
        uri = self.uri_for(entity)
        DeleteJob.__init__(self, uri)

class SetPropertyJob(PutJob):

    def __init__(self, entity, key, value):
        uri = self.uri_for(entity, "properties", key)
        PutJob.__init__(self, uri, value)

class SetPropertiesJob(PutJob):

    def __init__(self, entity, properties):
        uri = self.uri_for(entity, "properties")
        PutJob.__init__(self, uri, PropertySet(properties))

class DeletePropertyJob(DeleteJob):

    def __init__(self, entity, key):
        uri = self.uri_for(entity, "properties", key)
        DeleteJob.__init__(self, uri)

class DeletePropertiesJob(DeleteJob):

    def __init__(self, entity):
        uri = self.uri_for(entity, "properties")
        DeleteJob.__init__(self, uri)

class AddLabelsJob(PostJob):

    def __init__(self, node, *labels):
        uri = self.uri_for(node, "labels")
        PostJob.__init__(self, uri, LabelSet(labels))

class RemoveLabelJob(DeleteJob):

    def __init__(self, entity, label):
        uri = self.uri_for(entity, "labels", label)
        DeleteJob.__init__(self, uri)

class SetLabelsJob(PutJob):

    def __init__(self, entity, *labels):
        uri = self.uri_for(entity, "labels")
        PutJob.__init__(self, uri, LabelSet(labels))

class WriteBatch(Batch):
    """ Generic batch execution facility for data write requests. Most methods
    return a :py:class:`BatchRequest <py2neo.neo4j.BatchRequest>` object that
    can be used as a reference in other methods. See the
    :py:meth:`create <py2neo.neo4j.WriteBatch.create>` method for an example
    of this.
    """

    def __init__(self, graph):
        Batch.__init__(self, graph)

    def create(self, abstract):
        """ Create a node or relationship based on the abstract entity
        provided. For example::

            batch = WriteBatch(graph)
            a = batch.create(node(name="Alice"))
            b = batch.create(node(name="Bob"))
            batch.create(rel(a, "KNOWS", b))
            results = batch.submit()

        :param abstract: node or relationship
        :type abstract: abstract
        :return: batch request object
        """
        entity = self.graph.cast(abstract)
        if isinstance(entity, Node):
            return self.append(CreateNodeJob(**entity.properties))
        elif isinstance(entity, Relationship):
            start_node = self.resolve(entity.start_node)
            end_node = self.resolve(entity.end_node)
            return self.append(CreateRelationshipJob(start_node, entity.rel, end_node))
        else:
            raise TypeError(entity)

    def create_path(self, node, *rels_and_nodes):
        """ Construct a path across a specified set of nodes and relationships.
        Nodes may be existing concrete node instances, abstract nodes or
        :py:const:`None` but references to other requests are not supported.

        :param node: start node
        :type node: concrete, abstract or :py:const:`None`
        :param rels_and_nodes: alternating relationships and nodes
        :type rels_and_nodes: concrete, abstract or :py:const:`None`
        :return: batch request object
        """
        return self.append(CreatePathJob(node, *rels_and_nodes))

    def get_or_create_path(self, node, *rels_and_nodes):
        """ Construct a unique path across a specified set of nodes and
        relationships, adding only parts that are missing. Nodes may be
        existing concrete node instances, abstract nodes or :py:const:`None`
        but references to other requests are not supported.

        :param node: start node
        :type node: concrete, abstract or :py:const:`None`
        :param rels_and_nodes: alternating relationships and nodes
        :type rels_and_nodes: concrete, abstract or :py:const:`None`
        :return: batch request object
        """
        return self.append(MergePathJob(node, *rels_and_nodes))

    def delete(self, entity):
        """ Delete a node or relationship from the graph.

        :param entity: node or relationship to delete
        :type entity: concrete or reference
        :return: batch request object
        """
        return self.append(DeleteEntityJob(self.resolve(entity)))

    def set_property(self, entity, key, value):
        """ Set a single property on a node or relationship.

        :param entity: node or relationship on which to set property
        :type entity: concrete or reference
        :param key: property key
        :type key: :py:class:`str`
        :param value: property value
        :return: batch request object
        """
        return self.append(SetPropertyJob(self.resolve(entity), key, value))

    def set_properties(self, entity, properties):
        """ Replace all properties on a node or relationship.

        :param entity: node or relationship on which to set properties
        :type entity: concrete or reference
        :param properties: properties
        :type properties: :py:class:`dict`
        :return: batch request object
        """
        return self.append(SetPropertiesJob(self.resolve(entity), properties))

    def delete_property(self, entity, key):
        """ Delete a single property from a node or relationship.

        :param entity: node or relationship from which to delete property
        :type entity: concrete or reference
        :param key: property key
        :type key: :py:class:`str`
        :return: batch request object
        """
        return self.append(DeletePropertyJob(self.resolve(entity), key))

    def delete_properties(self, entity):
        """ Delete all properties from a node or relationship.

        :param entity: node or relationship from which to delete properties
        :type entity: concrete or reference
        :return: batch request object
        """
        return self.append(DeletePropertiesJob(self.resolve(entity)))

    def add_labels(self, node, *labels):
        """ Add labels to a node.

        :param node: node to which to add labels
        :type entity: concrete or reference
        :param labels: text labels
        :type labels: :py:class:`str`
        :return: batch request object
        """
        return self.append(AddLabelsJob(self.resolve(node), *labels))

    def remove_label(self, node, label):
        """ Remove a label from a node.

        :param node: node from which to remove labels (can be a reference to
            another request within the same batch)
        :param label: text label
        :type label: :py:class:`str`
        :return: batch request object
        """
        return self.append(RemoveLabelJob(self.resolve(node), label))

    def set_labels(self, node, *labels):
        """ Replace all labels on a node.

        :param node: node on which to replace labels (can be a reference to
            another request within the same batch)
        :param labels: text labels
        :type labels: :py:class:`str`
        :return: batch request object
        """
        return self.append(SetLabelsJob(self.resolve(node), *labels))

    # TODO: PullBatch
    # TODO: PushBatch
<commit_msg>set -> list<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright 2011-2014, Nigel Small
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import division, unicode_literals

from py2neo.core import Node, Relationship, Path, PropertySet, LabelSet
from py2neo.util import compact
from py2neo.batch.core import Batch, PostJob, CypherJob, DeleteJob, PutJob

# TODO: find a better home for this method
def _create_query(p, unique):
    nodes, path, values, params = [], [], [], {}

    def append_node(i, node):
        if node is None:
            path.append("(n{0})".format(i))
            values.append("n{0}".format(i))
        elif node.bound:
            path.append("(n{0})".format(i))
            nodes.append("n{0}=node({{i{0}}})".format(i))
            params["i{0}".format(i)] = node._id
            values.append("n{0}".format(i))
        else:
            path.append("(n{0} {{p{0}}})".format(i))
            params["p{0}".format(i)] = node.properties
            values.append("n{0}".format(i))

    def append_rel(i, rel):
        if rel.properties:
            path.append("-[r{0}:`{1}` {{q{0}}}]->".format(i, rel.type))
            params["q{0}".format(i)] = compact(rel.properties)
            values.append("r{0}".format(i))
        else:
            path.append("-[r{0}:`{1}`]->".format(i, rel.type))
            values.append("r{0}".format(i))

    append_node(0, p.nodes[0])
    for i, rel in enumerate(p.rels):
        append_rel(i, rel)
        append_node(i + 1, p.nodes[i + 1])
    clauses = []
    if nodes:
        clauses.append("START {0}".format(",".join(nodes)))
    if unique:
        clauses.append("CREATE UNIQUE p={0}".format("".join(path)))
    else:
        clauses.append("CREATE p={0}".format("".join(path)))
    #clauses.append("RETURN {0}".format(",".join(values)))
    clauses.append("RETURN p")
    query = " ".join(clauses)
    return query, params

class CreateNodeJob(PostJob):

    def __init__(self, **properties):
        PostJob.__init__(self, "node", properties)

class CreateRelationshipJob(PostJob):

    def __init__(self, start_node, rel, end_node, **properties):
        uri = self.uri_for(start_node, "relationships")
        body = {"type": rel.type, "to": self.uri_for(end_node)}
        if rel.properties or properties:
            body["data"] = dict(rel.properties, **properties)
        PostJob.__init__(self, uri, body)

class CreatePathJob(CypherJob):

    def __init__(self, *entities):
        CypherJob.__init__(self, *_create_query(Path(*entities), unique=False))

class MergePathJob(CypherJob):

    def __init__(self, *entities):
        CypherJob.__init__(self, *_create_query(Path(*entities), unique=True))

class DeleteEntityJob(DeleteJob):

    def __init__(self, entity):
        uri = self.uri_for(entity)
        DeleteJob.__init__(self, uri)

class SetPropertyJob(PutJob):

    def __init__(self, entity, key, value):
        uri = self.uri_for(entity, "properties", key)
        PutJob.__init__(self, uri, value)

class SetPropertiesJob(PutJob):

    def __init__(self, entity, properties):
        uri = self.uri_for(entity, "properties")
        PutJob.__init__(self, uri, PropertySet(properties))

class DeletePropertyJob(DeleteJob):

    def __init__(self, entity, key):
        uri = self.uri_for(entity, "properties", key)
        DeleteJob.__init__(self, uri)

class DeletePropertiesJob(DeleteJob):

    def __init__(self, entity):
        uri = self.uri_for(entity, "properties")
        DeleteJob.__init__(self, uri)

class AddLabelsJob(PostJob):

    def __init__(self, node, *labels):
        uri = self.uri_for(node, "labels")
        PostJob.__init__(self, uri, list(LabelSet(labels)))

class RemoveLabelJob(DeleteJob):

    def __init__(self, entity, label):
        uri = self.uri_for(entity, "labels", label)
        DeleteJob.__init__(self, uri)

class SetLabelsJob(PutJob):

    def __init__(self, entity, *labels):
        uri = self.uri_for(entity, "labels")
        PutJob.__init__(self, uri, list(LabelSet(labels)))

class WriteBatch(Batch):
    """ Generic batch execution facility for data write requests. Most methods
    return a :py:class:`BatchRequest <py2neo.neo4j.BatchRequest>` object that
    can be used as a reference in other methods. See the
    :py:meth:`create <py2neo.neo4j.WriteBatch.create>` method for an example
    of this.
    """

    def __init__(self, graph):
        Batch.__init__(self, graph)

    def create(self, abstract):
        """ Create a node or relationship based on the abstract entity
        provided. For example::

            batch = WriteBatch(graph)
            a = batch.create(node(name="Alice"))
            b = batch.create(node(name="Bob"))
            batch.create(rel(a, "KNOWS", b))
            results = batch.submit()

        :param abstract: node or relationship
        :type abstract: abstract
        :return: batch request object
        """
        entity = self.graph.cast(abstract)
        if isinstance(entity, Node):
            return self.append(CreateNodeJob(**entity.properties))
        elif isinstance(entity, Relationship):
            start_node = self.resolve(entity.start_node)
            end_node = self.resolve(entity.end_node)
            return self.append(CreateRelationshipJob(start_node, entity.rel, end_node))
        else:
            raise TypeError(entity)

    def create_path(self, node, *rels_and_nodes):
        """ Construct a path across a specified set of nodes and relationships.
        Nodes may be existing concrete node instances, abstract nodes or
        :py:const:`None` but references to other requests are not supported.

        :param node: start node
        :type node: concrete, abstract or :py:const:`None`
        :param rels_and_nodes: alternating relationships and nodes
        :type rels_and_nodes: concrete, abstract or :py:const:`None`
        :return: batch request object
        """
        return self.append(CreatePathJob(node, *rels_and_nodes))

    def get_or_create_path(self, node, *rels_and_nodes):
        """ Construct a unique path across a specified set of nodes and
        relationships, adding only parts that are missing. Nodes may be
        existing concrete node instances, abstract nodes or :py:const:`None`
        but references to other requests are not supported.

        :param node: start node
        :type node: concrete, abstract or :py:const:`None`
        :param rels_and_nodes: alternating relationships and nodes
        :type rels_and_nodes: concrete, abstract or :py:const:`None`
        :return: batch request object
        """
        return self.append(MergePathJob(node, *rels_and_nodes))

    def delete(self, entity):
        """ Delete a node or relationship from the graph.

        :param entity: node or relationship to delete
        :type entity: concrete or reference
        :return: batch request object
        """
        return self.append(DeleteEntityJob(self.resolve(entity)))

    def set_property(self, entity, key, value):
        """ Set a single property on a node or relationship.

        :param entity: node or relationship on which to set property
        :type entity: concrete or reference
        :param key: property key
        :type key: :py:class:`str`
        :param value: property value
        :return: batch request object
        """
        return self.append(SetPropertyJob(self.resolve(entity), key, value))

    def set_properties(self, entity, properties):
        """ Replace all properties on a node or relationship.

        :param entity: node or relationship on which to set properties
        :type entity: concrete or reference
        :param properties: properties
        :type properties: :py:class:`dict`
        :return: batch request object
        """
        return self.append(SetPropertiesJob(self.resolve(entity), properties))

    def delete_property(self, entity, key):
        """ Delete a single property from a node or relationship.

        :param entity: node or relationship from which to delete property
        :type entity: concrete or reference
        :param key: property key
        :type key: :py:class:`str`
        :return: batch request object
        """
        return self.append(DeletePropertyJob(self.resolve(entity), key))

    def delete_properties(self, entity):
        """ Delete all properties from a node or relationship.

        :param entity: node or relationship from which to delete properties
        :type entity: concrete or reference
        :return: batch request object
        """
        return self.append(DeletePropertiesJob(self.resolve(entity)))

    def add_labels(self, node, *labels):
        """ Add labels to a node.

        :param node: node to which to add labels
        :type entity: concrete or reference
        :param labels: text labels
        :type labels: :py:class:`str`
        :return: batch request object
        """
        return self.append(AddLabelsJob(self.resolve(node), *labels))

    def remove_label(self, node, label):
        """ Remove a label from a node.

        :param node: node from which to remove labels (can be a reference to
            another request within the same batch)
        :param label: text label
        :type label: :py:class:`str`
        :return: batch request object
        """
        return self.append(RemoveLabelJob(self.resolve(node), label))

    def set_labels(self, node, *labels):
        """ Replace all labels on a node.

        :param node: node on which to replace labels (can be a reference to
            another request within the same batch)
        :param labels: text labels
        :type labels: :py:class:`str`
        :return: batch request object
        """
        return self.append(SetLabelsJob(self.resolve(node), *labels))

    # TODO: PullBatch
    # TODO: PushBatch
<|endoftext|>


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

# Copyright 2011-2017, Nigel Small
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from sys import stdin, stdout, stderr

from py2neo.compat import number

def black(s):
    return "\x1b[30m{:s}\x1b[0m".format(s)

def red(s):
    return "\x1b[31m{:s}\x1b[0m".format(s)

def green(s):
    return "\x1b[32m{:s}\x1b[0m".format(s)

def yellow(s):
    return "\x1b[33m{:s}\x1b[0m".format(s)

def blue(s):
    return "\x1b[34m{:s}\x1b[0m".format(s)

def magenta(s):
    return "\x1b[35m{:s}\x1b[0m".format(s)

def cyan(s):
    return "\x1b[36m{:s}\x1b[0m".format(s)

def white(s):
    return "\x1b[37m{:s}\x1b[0m".format(s)

def bright_black(s):
    return "\x1b[30;1m{:s}\x1b[0m".format(s)

def bright_red(s):
    return "\x1b[31;1m{:s}\x1b[0m".format(s)

def bright_green(s):
    return "\x1b[32;1m{:s}\x1b[0m".format(s)

def bright_yellow(s):
    return "\x1b[33;1m{:s}\x1b[0m".format(s)

def bright_blue(s):
    return "\x1b[34;1m{:s}\x1b[0m".format(s)

def bright_magenta(s):
    return "\x1b[35;1m{:s}\x1b[0m".format(s)

def bright_cyan(s):
    return "\x1b[36;1m{:s}\x1b[0m".format(s)

def bright_white(s):
    return "\x1b[37;1m{:s}\x1b[0m".format(s)

class Table(object):

    def __init__(self, console, str_function=str, auto_align=False, header_rows=0, header_columns=0):
        self.console = console
        self._rows = []
        self._widths = []
        self.str_function = str_function
        self.auto_align = auto_align
        self.header_rows = header_rows
        self.header_columns = header_columns

    def append(self, values):
        self._rows.append(tuple(values))

    def _calc_widths(self):
        s = self.str_function
        w = self._widths
        w[:] = ()
        for values in self._rows:
            while len(w) < len(values):
                w.append(0)
            for i, value in enumerate(values):
                w[i] = max(w[i], len(s(value)))

    def write(self):
        self._calc_widths()
        widths = self._widths
        if not widths:
            return

        if self.console.can_write_colour_out():
            colour = cyan
        else:
            colour = lambda x: x

        table = []

        def row_join(values, is_header_row):
            r = []
            for i, value in enumerate(values):
                if i > 0:
                    if i == self.header_columns:
                        gap = u" : "
                        if not is_header_row:
                            gap = colour(gap)
                    else:
                        gap = u"  "
                    r.append(gap)
                r.append(value)
            return u"".join(r)

        s = self.str_function
        for y, values in enumerate(self._rows):
            is_header_row = y < self.header_rows
            row = []
            for x, value in enumerate(values):
                if self.auto_align and isinstance(value, number):
                    template = u"{:>%d}" % widths[x]
                else:
                    template = u"{:<%d}" % widths[x]
                cell = template.format(s(value))
                if not is_header_row and x < self.header_columns:
                    cell = colour(cell)
                row.append(cell)
            if is_header_row:
                table.append(colour(row_join(row, is_header_row)))
                if y == self.header_rows - 1:
                    hr = []
                    for width in self._widths:
                        hr.append(width * "-")
                    table.append(colour(row_join(hr, is_header_row)))
            else:
                table.append(row_join(row, is_header_row))
        self.console.write(u"\n".join(table))

class Console(object):

    def __init__(self, in_stream=None, out_stream=None, err_stream=None, use_colour=True, out_encoding="utf-8"):
        self.in_stream = in_stream or stdin
        self.out_stream = out_stream or stdout
        self.err_stream = err_stream or stderr
        self.use_colour = use_colour
        self.out_encoding = out_encoding

    def can_write_colour_out(self):
        return self.use_colour and self.out_stream.isatty()

    def can_write_colour_err(self):
        return self.use_colour and self.err_stream.isatty()

    def write(self, s="", end="\n"):
        self.out_stream.write(s.encode(self.out_encoding))
        self.out_stream.write(end.encode(self.out_encoding))

    def write_metadata(self, s="", end="\n"):
        if self.can_write_colour_out():
            s = cyan(s)
        self.write(s, end=end)

    def write_err(self, s="", end="\n"):
        self.err_stream.write(s)
        self.err_stream.write(end)

    def write_help(self, s="", end="\n"):
        self.write_err(s, end)

    def write_error(self, s="", end="\n"):
        if self.can_write_colour_err():
            s = yellow(s)
        self.write_err(s, end)
<commit_msg>red error messages<commit_after>#!/usr/bin/env python
# -*- encoding: utf-8 -*-

# Copyright 2011-2017, Nigel Small
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from sys import stdin, stdout, stderr

from py2neo.compat import number

def black(s):
    return "\x1b[30m{:s}\x1b[0m".format(s)

def red(s):
    return "\x1b[31m{:s}\x1b[0m".format(s)

def green(s):
    return "\x1b[32m{:s}\x1b[0m".format(s)

def yellow(s):
    return "\x1b[33m{:s}\x1b[0m".format(s)

def blue(s):
    return "\x1b[34m{:s}\x1b[0m".format(s)

def magenta(s):
    return "\x1b[35m{:s}\x1b[0m".format(s)

def cyan(s):
    return "\x1b[36m{:s}\x1b[0m".format(s)

def white(s):
    return "\x1b[37m{:s}\x1b[0m".format(s)

def bright_black(s):
    return "\x1b[30;1m{:s}\x1b[0m".format(s)

def bright_red(s):
    return "\x1b[31;1m{:s}\x1b[0m".format(s)

def bright_green(s):
    return "\x1b[32;1m{:s}\x1b[0m".format(s)

def bright_yellow(s):
    return "\x1b[33;1m{:s}\x1b[0m".format(s)

def bright_blue(s):
    return "\x1b[34;1m{:s}\x1b[0m".format(s)

def bright_magenta(s):
    return "\x1b[35;1m{:s}\x1b[0m".format(s)

def bright_cyan(s):
    return "\x1b[36;1m{:s}\x1b[0m".format(s)

def bright_white(s):
    return "\x1b[37;1m{:s}\x1b[0m".format(s)

class Table(object):

    def __init__(self, console, str_function=str, auto_align=False, header_rows=0, header_columns=0):
        self.console = console
        self._rows = []
        self._widths = []
        self.str_function = str_function
        self.auto_align = auto_align
        self.header_rows = header_rows
        self.header_columns = header_columns

    def append(self, values):
        self._rows.append(tuple(values))

    def _calc_widths(self):
        s = self.str_function
        w = self._widths
        w[:] = ()
        for values in self._rows:
            while len(w) < len(values):
                w.append(0)
            for i, value in enumerate(values):
                w[i] = max(w[i], len(s(value)))

    def write(self):
        self._calc_widths()
        widths = self._widths
        if not widths:
            return

        if self.console.can_write_colour_out():
            colour = cyan
        else:
            colour = lambda x: x

        table = []

        def row_join(values, is_header_row):
            r = []
            for i, value in enumerate(values):
                if i > 0:
                    if i == self.header_columns:
                        gap = u" : "
                        if not is_header_row:
                            gap = colour(gap)
                    else:
                        gap = u"  "
                    r.append(gap)
                r.append(value)
            return u"".join(r)

        s = self.str_function
        for y, values in enumerate(self._rows):
            is_header_row = y < self.header_rows
            row = []
            for x, value in enumerate(values):
                if self.auto_align and isinstance(value, number):
                    template = u"{:>%d}" % widths[x]
                else:
                    template = u"{:<%d}" % widths[x]
                cell = template.format(s(value))
                if not is_header_row and x < self.header_columns:
                    cell = colour(cell)
                row.append(cell)
            if is_header_row:
                table.append(colour(row_join(row, is_header_row)))
                if y == self.header_rows - 1:
                    hr = []
                    for width in self._widths:
                        hr.append(width * "-")
                    table.append(colour(row_join(hr, is_header_row)))
            else:
                table.append(row_join(row, is_header_row))
        self.console.write(u"\n".join(table))

class Console(object):

    def __init__(self, in_stream=None, out_stream=None, err_stream=None, use_colour=True, out_encoding="utf-8"):
        self.in_stream = in_stream or stdin
        self.out_stream = out_stream or stdout
        self.err_stream = err_stream or stderr
        self.use_colour = use_colour
        self.out_encoding = out_encoding

    def can_write_colour_out(self):
        return self.use_colour and self.out_stream.isatty()

    def can_write_colour_err(self):
        return self.use_colour and self.err_stream.isatty()

    def write(self, s="", end="\n"):
        self.out_stream.write(s.encode(self.out_encoding))
        self.out_stream.write(end.encode(self.out_encoding))

    def write_metadata(self, s="", end="\n"):
        if self.can_write_colour_out():
            s = cyan(s)
        self.write(s, end=end)

    def write_err(self, s="", end="\n"):
        self.err_stream.write(s)
        self.err_stream.write(end)

    def write_help(self, s="", end="\n"):
        self.write_err(s, end)

    def write_error(self, s="", end="\n"):
        if self.can_write_colour_err():
            s = red(s)
        self.write_err(s, end)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''python library for accessing the openHAB REST API'''

#
# Georges Toth (c) 2016 <georges@trypill.org>
#
# python-openhab is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# python-openhab 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with python-openhab.  If not, see <http://www.gnu.org/licenses/>.
#

# pylint: disable=bad-indentation

import requests
from items import *

__author__ = 'Georges Toth <georges@trypill.org>'
__license__ = 'AGPLv3+'

class openHAB(object):
  def __init__(self, base_url):
    self.base_url = base_url

    self.session = requests.Session()
    self.session.headers['accept'] = 'application/json'

  def _check_req_return(self, req):
    if not (req.status_code >= 200 and req.status_code < 300):
      req.raise_for_status()

  def req_get(self, uri_path):
    r = self.session.get(self.base_url + uri_path)
    self._check_req_return(r)
    return r.json()

  def req_post(self, uri_path, data=None):
    r = self.session.post(self.base_url + uri_path, data=data)
    self._check_req_return(r)

  def req_put(self, uri_path, data=None):
    r = self.session.put(self.base_url + uri_path, data=data)
    self._check_req_return(r)

  # fetch all items
  def fetch_all_items(self):
    '''Returns all items defined in openHAB except for group-items'''
    items = {}
    res = self.req_get('/items/')

    for i in res:
      # we ignore group-items for now
      if i['type'] == 'Group':
        continue

      if not i['name'] in items:
        items[i['name']] = self.json_to_item(i)

    return items

  def get_item(self, name):
    '''Returns an item with its state and type as fetched from openHAB'''
    json_data = self.get_item_raw(name)

    return self.json_to_item(json_data)

  def json_to_item(self, json_data):
    if json_data['type'] == 'Switch':
      return SwitchItem(self, json_data)
    elif json_data['type'] == 'DateTime':
      return DateTimeItem(self, json_data)
    elif json_data['type'] == 'Contact':
      return ContactItem(self, json_data)
    else:
      return Item(self, json_data)

  def get_item_raw(self, name):
    '''Private method for fetching a json configuration of an item'''
    return self.req_get('/items/' + name)
<commit_msg>add support for number item<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''python library for accessing the openHAB REST API'''

#
# Georges Toth (c) 2016 <georges@trypill.org>
#
# python-openhab is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# python-openhab 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with python-openhab.  If not, see <http://www.gnu.org/licenses/>.
#

# pylint: disable=bad-indentation

import requests
from items import *

__author__ = 'Georges Toth <georges@trypill.org>'
__license__ = 'AGPLv3+'

class openHAB(object):
  def __init__(self, base_url):
    self.base_url = base_url

    self.session = requests.Session()
    self.session.headers['accept'] = 'application/json'

  def _check_req_return(self, req):
    if not (req.status_code >= 200 and req.status_code < 300):
      req.raise_for_status()

  def req_get(self, uri_path):
    r = self.session.get(self.base_url + uri_path)
    self._check_req_return(r)
    return r.json()

  def req_post(self, uri_path, data=None):
    r = self.session.post(self.base_url + uri_path, data=data)
    self._check_req_return(r)

  def req_put(self, uri_path, data=None):
    r = self.session.put(self.base_url + uri_path, data=data)
    self._check_req_return(r)

  # fetch all items
  def fetch_all_items(self):
    '''Returns all items defined in openHAB except for group-items'''
    items = {}
    res = self.req_get('/items/')

    for i in res:
      # we ignore group-items for now
      if i['type'] == 'Group':
        continue

      if not i['name'] in items:
        items[i['name']] = self.json_to_item(i)

    return items

  def get_item(self, name):
    '''Returns an item with its state and type as fetched from openHAB'''
    json_data = self.get_item_raw(name)

    return self.json_to_item(json_data)

  def json_to_item(self, json_data):
    if json_data['type'] == 'Switch':
      return SwitchItem(self, json_data)
    elif json_data['type'] == 'DateTime':
      return DateTimeItem(self, json_data)
    elif json_data['type'] == 'Contact':
      return ContactItem(self, json_data)
    elif json_data['type'] == 'Number':
      return NumberItem(self, json_data)
    else:
      return Item(self, json_data)

  def get_item_raw(self, name):
    '''Private method for fetching a json configuration of an item'''
    return self.req_get('/items/' + name)
<|endoftext|>


<commit_before># Python packages
import numpy as np
import matplotlib.pyplot as plt

# LSST stack software
import lsst.sims.photUtils.Bandpass as Bandpass

class Atmo():
    def __init__(self, parameters, airmass, wavelength, transmission, aerosolNormCoeff, aerosolNormWavelen):
        # Airmass
    	self.X = airmass
        # List of parameters
    	self.P = parameters
        # List of wavelengths
        self.wavelen = wavelength
        # Aerosol normalization coefficient used to model aerosol transmission profile
    	self.aerosolNormCoeff = aerosolNormCoeff
        # Aerosol normalization wavelength used to model aerosol transmission profile
    	self.aerosolNormWavelen = aerosolNormWavelen
        # List of components
    	self.components = ['H2O','O2','O3','Rayleigh','Aerosol']
        # List of total transmission profiles
    	self.sb = None
        # Component-keyed dictionary of transmission profiles
    	self.sbDict = None
        # Build atmosphere and return object
    	self.__buildAtmo(parameters, airmass, transmission, aerosolNormCoeff, aerosolNormWavelen)

    def __buildAtmo(self, parameters, airmass, transmission, aerosolNormCoeff, aerosolNormWavelen):
        """Builds an atmospheric transmission profile given a set of component parameters and
        returns bandpass object. (S^{atm})"""

        parameters = np.array(parameters)

        transDict = {}

        transDict['H2O'] = transmission[airmass]['H2O']**parameters[0]
        transDict['O2'] = transmission[airmass]['O2']**parameters[1]
        transDict['O3'] = transmission[airmass]['O3']**parameters[2]
        transDict['Rayleigh'] = transmission[airmass]['Rayleigh']**parameters[3]
        transDict['Aerosol'] = self.__aerosol(self.wavelen, airmass, parameters[5], aerosolNormCoeff, aerosolNormWavelen)**parameters[4]
        totalTrans = transDict['H2O']*transDict['O2']*transDict['O3']*transDict['Rayleigh']*transDict['Aerosol']

        atmo = Bandpass(wavelen=self.wavelen,sb=totalTrans)

        self.sb = atmo.sb
        self.sbDict = transDict

        return

    def __aerosol(self, wavelength, airmass, alpha, aerosolNormCoeff, aerosolNormWavelen):
        """Standard aerosol transmission function, returns array of transmission values over a range of
            wavelengths."""
        return np.e**(-aerosolNormCoeff * airmass * (aerosolNormWavelen / wavelength) * alpha)<commit_msg>removed matplotlib import<commit_after># Python packages
import numpy as np

# LSST stack software
import lsst.sims.photUtils.Bandpass as Bandpass

class Atmo():
    def __init__(self, parameters, airmass, wavelength, transmission, aerosolNormCoeff, aerosolNormWavelen):
        # Airmass
    	self.X = airmass
        # List of parameters
    	self.P = parameters
        # List of wavelengths
        self.wavelen = wavelength
        # Aerosol normalization coefficient used to model aerosol transmission profile
    	self.aerosolNormCoeff = aerosolNormCoeff
        # Aerosol normalization wavelength used to model aerosol transmission profile
    	self.aerosolNormWavelen = aerosolNormWavelen
        # List of components
    	self.components = ['H2O','O2','O3','Rayleigh','Aerosol']
        # List of total transmission profiles
    	self.sb = None
        # Component-keyed dictionary of transmission profiles
    	self.sbDict = None
        # Build atmosphere and return object
    	self.__buildAtmo(parameters, airmass, transmission, aerosolNormCoeff, aerosolNormWavelen)

    def __buildAtmo(self, parameters, airmass, transmission, aerosolNormCoeff, aerosolNormWavelen):
        """Builds an atmospheric transmission profile given a set of component parameters and
        returns bandpass object. (S^{atm})"""

        parameters = np.array(parameters)

        transDict = {}

        transDict['H2O'] = transmission[airmass]['H2O']**parameters[0]
        transDict['O2'] = transmission[airmass]['O2']**parameters[1]
        transDict['O3'] = transmission[airmass]['O3']**parameters[2]
        transDict['Rayleigh'] = transmission[airmass]['Rayleigh']**parameters[3]
        transDict['Aerosol'] = self.__aerosol(self.wavelen, airmass, parameters[5], aerosolNormCoeff, aerosolNormWavelen)**parameters[4]
        totalTrans = transDict['H2O']*transDict['O2']*transDict['O3']*transDict['Rayleigh']*transDict['Aerosol']

        atmo = Bandpass(wavelen=self.wavelen,sb=totalTrans)

        self.sb = atmo.sb
        self.sbDict = transDict

        return

    def __aerosol(self, wavelength, airmass, alpha, aerosolNormCoeff, aerosolNormWavelen):
        """Standard aerosol transmission function, returns array of transmission values over a range of
            wavelengths."""
        return np.e**(-aerosolNormCoeff * airmass * (aerosolNormWavelen / wavelength) * alpha)<|endoftext|>


<commit_before># -*- test-case-name: openid.test.test_fetchers -*-
"""
This module contains the HTTP fetcher interface and several implementations.
"""

__all__ = ['fetch', 'getDefaultFetcher', 'setDefaultFetcher', 'HTTPResponse']

import urllib.request
import urllib.error
import urllib.parse
import http.client

import sys
import contextlib

import openid

USER_AGENT = "python-openid/%s (%s)" % (openid.__version__, sys.platform)
MAX_RESPONSE_KB = 1024

def fetch(url, body=None, headers=None):
    """Invoke the fetch method on the default fetcher. Most users
    should need only this method.

    @raises Exception: any exceptions that may be raised by the default fetcher
    """
    fetcher = getDefaultFetcher()
    return fetcher.fetch(url, body, headers)

# Contains the currently set HTTP fetcher. If it is set to None, the
# library will call Urllib2Fetcher() to set it. Do not access this
# variable outside of this module.
_default_fetcher = None

def getDefaultFetcher():
    """Return the default fetcher instance
    if no fetcher has been set, it will create a default fetcher.

    @return: the default fetcher
    @rtype: HTTPFetcher
    """
    global _default_fetcher

    if _default_fetcher is None:
        setDefaultFetcher(Urllib2Fetcher())

    return _default_fetcher

def setDefaultFetcher(fetcher):
    """Set the default fetcher

    @param fetcher: The fetcher to use as the default HTTP fetcher
    @type fetcher: HTTPFetcher
    """
    global _default_fetcher
    _default_fetcher = fetcher

class HTTPResponse(object):
    """XXX document attributes"""
    headers = None
    status = None
    body = None
    final_url = None

    def __init__(self, final_url=None, status=None, headers=None, body=None):
        self.final_url = final_url
        self.status = status
        self.headers = headers
        self.body = body

    def __repr__(self):
        return "<%s status %s for %s>" % (self.__class__.__name__,
                                          self.status,
                                          self.final_url)

def _allowedURL(url):
    parsed = urllib.parse.urlparse(url)
    # scheme is the first item in the tuple
    return parsed[0] in ('http', 'https')

def _makeResponse(urllib2_response):
    '''
    Construct an HTTPResponse from the the urllib response. Attempt to
    decode the response body from bytes to str if the necessary information
    is available.
    '''
    resp = HTTPResponse()
    resp.body = urllib2_response.read(MAX_RESPONSE_KB * 1024)
    resp.final_url = urllib2_response.geturl()
    resp.headers = {k.lower(): v for k, v in urllib2_response.info().items()}

    if hasattr(urllib2_response, 'code'):
        resp.status = urllib2_response.code
    else:
        resp.status = 200

    return resp

class Urllib2Fetcher:
    """An C{L{HTTPFetcher}} that uses urllib2.
    """

    # Parameterized for the benefit of testing frameworks, see
    # http://trac.openidenabled.com/trac/ticket/85
    urlopen = staticmethod(urllib.request.urlopen)

    def fetch(self, url, body=None, headers=None):
        if not _allowedURL(url):
            raise urllib.error.URLError('Bad URL scheme: %r' % url)

        if headers is None:
            headers = {}

        headers.setdefault(
            'User-Agent',
            "%s Python-urllib/%s" % (USER_AGENT, urllib.request.__version__))

        if isinstance(body, str):
            body = bytes(body, encoding="utf-8")

        req = urllib.request.Request(url, data=body, headers=headers)

        url_resource = None
        try:
            url_resource = self.urlopen(req)
            with contextlib.closing(url_resource):
                return _makeResponse(url_resource)
        except urllib.error.HTTPError as why:
            with contextlib.closing(why):
                resp = _makeResponse(why)
                return resp
        except (urllib.error.URLError, http.client.BadStatusLine) as why:
            raise
        except Exception as why:
            raise AssertionError(why)

<commit_msg>Removing static alias "urlopen". Now Urllib2Fetcher is equivalent to a function.<commit_after># -*- test-case-name: openid.test.test_fetchers -*-
"""
This module contains the HTTP fetcher interface and several implementations.
"""

__all__ = ['fetch', 'getDefaultFetcher', 'setDefaultFetcher', 'HTTPResponse']

import urllib.request
import urllib.error
import urllib.parse
import http.client

import sys
import contextlib

import openid

USER_AGENT = "python-openid/%s (%s)" % (openid.__version__, sys.platform)
MAX_RESPONSE_KB = 1024

def fetch(url, body=None, headers=None):
    """Invoke the fetch method on the default fetcher. Most users
    should need only this method.

    @raises Exception: any exceptions that may be raised by the default fetcher
    """
    fetcher = getDefaultFetcher()
    return fetcher.fetch(url, body, headers)

# Contains the currently set HTTP fetcher. If it is set to None, the
# library will call Urllib2Fetcher() to set it. Do not access this
# variable outside of this module.
_default_fetcher = None

def getDefaultFetcher():
    """Return the default fetcher instance
    if no fetcher has been set, it will create a default fetcher.

    @return: the default fetcher
    @rtype: HTTPFetcher
    """
    global _default_fetcher

    if _default_fetcher is None:
        setDefaultFetcher(Urllib2Fetcher())

    return _default_fetcher

def setDefaultFetcher(fetcher):
    """Set the default fetcher

    @param fetcher: The fetcher to use as the default HTTP fetcher
    @type fetcher: HTTPFetcher
    """
    global _default_fetcher
    _default_fetcher = fetcher

class HTTPResponse(object):
    """XXX document attributes"""
    headers = None
    status = None
    body = None
    final_url = None

    def __init__(self, final_url=None, status=None, headers=None, body=None):
        self.final_url = final_url
        self.status = status
        self.headers = headers
        self.body = body

    def __repr__(self):
        return "<%s status %s for %s>" % (self.__class__.__name__,
                                          self.status,
                                          self.final_url)

def _allowedURL(url):
    parsed = urllib.parse.urlparse(url)
    # scheme is the first item in the tuple
    return parsed[0] in ('http', 'https')

def _makeResponse(urllib2_response):
    '''
    Construct an HTTPResponse from the the urllib response. Attempt to
    decode the response body from bytes to str if the necessary information
    is available.
    '''
    resp = HTTPResponse()
    resp.body = urllib2_response.read(MAX_RESPONSE_KB * 1024)
    resp.final_url = urllib2_response.geturl()
    resp.headers = {k.lower(): v for k, v in urllib2_response.info().items()}

    if hasattr(urllib2_response, 'code'):
        resp.status = urllib2_response.code
    else:
        resp.status = 200

    return resp

class Urllib2Fetcher:
    """An C{L{HTTPFetcher}} that uses urllib2.
    """

    def fetch(self, url, body=None, headers=None):
        if not _allowedURL(url):
            raise urllib.error.URLError('Bad URL scheme: %r' % url)

        if headers is None:
            headers = {}

        headers.setdefault(
            'User-Agent',
            "%s Python-urllib/%s" % (USER_AGENT, urllib.request.__version__))

        if isinstance(body, str):
            body = bytes(body, encoding="utf-8")

        req = urllib.request.Request(url, data=body, headers=headers)

        url_resource = None
        try:
            url_resource = urllib.request.urlopen(req)
            with contextlib.closing(url_resource):
                return _makeResponse(url_resource)
        except urllib.error.HTTPError as why:
            with contextlib.closing(why):
                resp = _makeResponse(why)
                return resp
        except (urllib.error.URLError, http.client.BadStatusLine) as why:
            raise
        except Exception as why:
            raise AssertionError(why)

<|endoftext|>