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

""" Simplified version of synchronizer that is working with alligned data. """

__author__ = "Yaroslav Litvinov"
__copyright__ = "Copyright 2016, Rackspace Inc."
__email__ = "yaroslav.litvinov@rackspace.com"

from logging import getLogger
from collections import namedtuple
from gizer.oplog_parser import OplogParser
from gizer.oplog_parser import exec_insert
from gizer.oplog_parser import Callback
from gizer.oplog_handlers import cb_insert
from gizer.oplog_handlers import cb_update
from gizer.oplog_handlers import cb_delete
from gizer.batch_comparator import ComparatorMongoPsql
from gizer.oplog_sync_base import OplogSyncBase
from gizer.oplog_sync_base import DO_OPLOG_READ_ATTEMPTS_COUNT
from mongo_reader.prepare_mongo_request import prepare_oplog_request

MAX_REQCOUNT_FOR_SHARD = 10000

class OplogSyncAllignedData(OplogSyncBase):
    """ Simplified version of synchronizer that is working with alligned data.

        As init load produces unalligned data this syncronizer should not be used
        just after init load finishes. Instead OplogSyncUnallignedData must be used.

    """

    def __init__(self, psql, mongo_readers, oplog_readers,
                 schemas_path, schema_engines, psql_schema):
        """ params:
        psql -- Postgres cursor wrapper
        mongo_readers -- dict of mongo readers, one per collection
        oplog -- Mongo oplog cursor wrappper
        schemas_path -- Path with js schemas representing mongo collections
        psql_schema -- psql schema whose tables data to patch."""
        super(OplogSyncAllignedData, self).\
            __init__(psql, mongo_readers, oplog_readers,
                     schemas_path, schema_engines, psql_schema)
        self.comparator = ComparatorMongoPsql(schema_engines,
                                              mongo_readers,
                                              psql,
                                              psql_schema)

    def __del__(self):
        del self.comparator

    def sync(self, start_ts_dict):
        """ Read oplog operations starting just after timestamp start_ts_dict
        by gathering timestamps from all configured shards.
        Apply oplog operations to psql db. After all records are applied do
        consistency check by comparing source (mongo) and dest(psql) records.
        Return False and do rollback if timestamps are applied but consistency
        checks are failed.
        Return sync points as dict.
        params:
        start_ts_dict -- dict with Timestamp for every shard. """

        new_ts_dict = start_ts_dict
        do_again_counter = 0
        do_again = True
        while do_again:
            do_again = False
            new_ts_dict = self.read_oplog_apply_ops(new_ts_dict, do_again_counter)
            compare_res = self.comparator.compare_src_dest()
            failed_attempts = self.comparator.get_failed_cmp_attempts()
            getLogger(__name__).warning("Failed cmp attempts %s" % failed_attempts)
            last_portion_failed = False
            # if failed only latest data
            if len(failed_attempts) == 1 and do_again_counter in failed_attempts:
                last_portion_failed = True
            if not compare_res or not new_ts_dict:
                # if transport returned an error then keep the same ts_start
                # and return True, as nothing applied
                readers_failed = [(k, v.failed) for k,v in \
                                    self.comparator.mongo_readers.iteritems() \
                                    if v.failed]
                if not new_ts_dict or len(readers_failed):
                    if len(readers_failed):
                        getLogger(__name__).warning("mongo readers failed: %s"
                                                    % (str(readers_failed)))
                        return start_ts_dict
                if last_portion_failed:
                    if do_again_counter < DO_OPLOG_READ_ATTEMPTS_COUNT:
                        do_again = True
                        do_again_counter += 1
                    else: # Attempts count exceeded
                        getLogger(__name__).warning('Attempts count exceeded.\
Force assigning compare_res to True.')
                        compare_res = True
                else:
                    getLogger(__name__).warning("Recs cmp failed.")
        if compare_res:
            getLogger(__name__).info('COMMIT')
            self.psql.conn.commit()
            return new_ts_dict
        else:
            getLogger(__name__).error('ROLLBACK')
            self.psql.conn.rollback()
            return None

    def read_oplog_apply_ops(self, start_ts_dict, attempt):
        """ Apply ops going after specified timestamps.
        params:
        start_ts_dict -- dict with Timestamp for every shard.
        Return updated sync points and dict containing affected record ids """
        # derive new sync point from starting points and update it on the go
        for name in self.oplog_readers:
            # get new timestamps greater than sync point
            js_oplog_query = prepare_oplog_request(start_ts_dict[name])
            self.oplog_readers[name].make_new_request(js_oplog_query)
            if self.oplog_readers[name].real_transport():
                self.oplog_readers[name].cursor.limit(MAX_REQCOUNT_FOR_SHARD)
        # create oplog parser. note: cb_insert doesn't need psql object
        parser = self.new_oplog_parser(dry_run=False)
        # go over oplog, and apply oplog ops for every timestamp
        oplog_queries = parser.next()
        while oplog_queries != None:
            collection_name = parser.item_info.schema_name
            rec_id = parser.item_info.rec_id
            self.oplog_rec_counter += 1
            for oplog_query in oplog_queries:
                self.queries_counter += 1
                exec_insert(self.psql, oplog_query)
                self.comparator.add_to_compare(collection_name, rec_id, attempt)
            oplog_queries = parser.next()
        getLogger(__name__).info(\
            "%d attempt. Handled oplog records/psql queries: %d/%d" %
            (attempt, self.oplog_rec_counter, self.queries_counter))
        res = {}
        for shard in start_ts_dict:
            if shard in parser.last_oplog_ts:
                # ts updated for this shard
                res[shard] = parser.last_oplog_ts[shard]
            else:
                # nothing received from this shard
                res[shard] = start_ts_dict[shard]
        if parser.is_failed():
            res = None
        return res

<commit_msg>log more timestamps<commit_after>#!/usr/bin/env python

""" Simplified version of synchronizer that is working with alligned data. """

__author__ = "Yaroslav Litvinov"
__copyright__ = "Copyright 2016, Rackspace Inc."
__email__ = "yaroslav.litvinov@rackspace.com"

from logging import getLogger
from collections import namedtuple
from gizer.oplog_parser import OplogParser
from gizer.oplog_parser import exec_insert
from gizer.oplog_parser import Callback
from gizer.oplog_handlers import cb_insert
from gizer.oplog_handlers import cb_update
from gizer.oplog_handlers import cb_delete
from gizer.batch_comparator import ComparatorMongoPsql
from gizer.oplog_sync_base import OplogSyncBase
from gizer.oplog_sync_base import DO_OPLOG_READ_ATTEMPTS_COUNT
from mongo_reader.prepare_mongo_request import prepare_oplog_request

MAX_REQCOUNT_FOR_SHARD = 10000

class OplogSyncAllignedData(OplogSyncBase):
    """ Simplified version of synchronizer that is working with alligned data.

        As init load produces unalligned data this syncronizer should not be used
        just after init load finishes. Instead OplogSyncUnallignedData must be used.

    """

    def __init__(self, psql, mongo_readers, oplog_readers,
                 schemas_path, schema_engines, psql_schema):
        """ params:
        psql -- Postgres cursor wrapper
        mongo_readers -- dict of mongo readers, one per collection
        oplog -- Mongo oplog cursor wrappper
        schemas_path -- Path with js schemas representing mongo collections
        psql_schema -- psql schema whose tables data to patch."""
        super(OplogSyncAllignedData, self).\
            __init__(psql, mongo_readers, oplog_readers,
                     schemas_path, schema_engines, psql_schema)
        self.comparator = ComparatorMongoPsql(schema_engines,
                                              mongo_readers,
                                              psql,
                                              psql_schema)

    def __del__(self):
        del self.comparator

    def sync(self, start_ts_dict):
        """ Read oplog operations starting just after timestamp start_ts_dict
        by gathering timestamps from all configured shards.
        Apply oplog operations to psql db. After all records are applied do
        consistency check by comparing source (mongo) and dest(psql) records.
        Return False and do rollback if timestamps are applied but consistency
        checks are failed.
        Return sync points as dict.
        params:
        start_ts_dict -- dict with Timestamp for every shard. """

        new_ts_dict = start_ts_dict
        do_again_counter = 0
        do_again = True
        while do_again:
            do_again = False
            new_ts_dict = self.read_oplog_apply_ops(new_ts_dict, do_again_counter)
            compare_res = self.comparator.compare_src_dest()
            failed_attempts = self.comparator.get_failed_cmp_attempts()
            getLogger(__name__).warning("Failed cmp attempts %s" % failed_attempts)
            last_portion_failed = False
            # if failed only latest data
            if len(failed_attempts) == 1 and do_again_counter in failed_attempts:
                last_portion_failed = True
            if not compare_res or not new_ts_dict:
                # if transport returned an error then keep the same ts_start
                # and return True, as nothing applied
                readers_failed = [(k, v.failed) for k,v in \
                                    self.comparator.mongo_readers.iteritems() \
                                    if v.failed]
                if not new_ts_dict or len(readers_failed):
                    if len(readers_failed):
                        getLogger(__name__).warning("mongo readers failed: %s"
                                                    % (str(readers_failed)))
                        return start_ts_dict
                if last_portion_failed:
                    if do_again_counter < DO_OPLOG_READ_ATTEMPTS_COUNT:
                        do_again = True
                        do_again_counter += 1
                    else: # Attempts count exceeded
                        getLogger(__name__).warning('Attempts count exceeded.\
Force assigning compare_res to True.')
                        compare_res = True
                else:
                    getLogger(__name__).warning("Recs cmp failed.")
        if compare_res:
            getLogger(__name__).info('COMMIT')
            self.psql.conn.commit()
            return new_ts_dict
        else:
            getLogger(__name__).error('ROLLBACK')
            self.psql.conn.rollback()
            return None

    def read_oplog_apply_ops(self, start_ts_dict, attempt):
        """ Apply ops going after specified timestamps.
        params:
        start_ts_dict -- dict with Timestamp for every shard.
        Return updated sync points and dict containing affected record ids """
        # derive new sync point from starting points and update it on the go
        for name in self.oplog_readers:
            # get new timestamps greater than sync point
            js_oplog_query = prepare_oplog_request(start_ts_dict[name])
            self.oplog_readers[name].make_new_request(js_oplog_query)
            if self.oplog_readers[name].real_transport():
                self.oplog_readers[name].cursor.limit(MAX_REQCOUNT_FOR_SHARD)
        # create oplog parser. note: cb_insert doesn't need psql object
        parser = self.new_oplog_parser(dry_run=False)
        # go over oplog, and apply oplog ops for every timestamp
        oplog_queries = parser.next()
        while oplog_queries != None:
            collection_name = parser.item_info.schema_name
            rec_id = parser.item_info.rec_id
            self.oplog_rec_counter += 1
            if len(oplog_queries):
                getLogger(__name__).info("Exec ts queries %s:",
                                         parser.item_info.ts)
            for oplog_query in oplog_queries:
                self.queries_counter += 1
                exec_insert(self.psql, oplog_query)
                self.comparator.add_to_compare(collection_name, rec_id, attempt)
            oplog_queries = parser.next()
        getLogger(__name__).info(\
            "%d attempt. Handled oplog records/psql queries: %d/%d" %
            (attempt, self.oplog_rec_counter, self.queries_counter))
        res = {}
        for shard in start_ts_dict:
            if shard in parser.last_oplog_ts:
                # ts updated for this shard
                res[shard] = parser.last_oplog_ts[shard]
            else:
                # nothing received from this shard
                res[shard] = start_ts_dict[shard]
        if parser.is_failed():
            res = None
        return res

<|endoftext|>


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

"""
:mod:`birthdayreminder` --- Birthday reminder plugin
====================================================
"""

from __future__ import unicode_literals

__docformat__ = "restructuredtext en"

import atexit
import datetime
from threading import Timer
import random
import operator

from Skype4Py.enums import cmsReceived

from plugin import Plugin
from output import ChatMessage

NOTIFICATION_TEMPLATE = "Today is {recipient} birthday! {interjection}!"

REMINDER_TEMPLATE = """
It was {last_recipient} birthday {last_delta} {last_day_or_days} ago.
{next_recipient} birthday is coming up next in {next_delta} {next_day_or_days}.
"""

INTERJECTIONS = [
    "Hurrah",
    "Hooray",
    "Huzzah",
    "Fancy",
    "Awesome",
    "Whee",
    "Whoa",
    "Unbelievable",
    "Wow",
    "Amazing",
    "Yahoo",
    "Yippie",
    "Yay",
    "Yeah",
    "Exciting",
    "Yee-haw",
    "Sweet",
    "Fantastic",
    "OMG",
]

def str_to_datetime(date_str):
    """
    >>> str_to_datetime("13.1")
    datetime.datetime(1900, 1, 13, 0, 0)

    >>> str_to_datetime("13.01")
    datetime.datetime(1900, 1, 13, 0, 0)

    >>> str_to_datetime("01.13")
    Traceback (most recent call last):
        ...
    ValueError: Input string does not match any known date format

    >>> str_to_datetime("13.1.2000")
    datetime.datetime(2000, 1, 13, 0, 0)

    >>> str_to_datetime("13.01.2000")
    datetime.datetime(2000, 1, 13, 0, 0)

    >>> str_to_datetime("01.13.2000")
    Traceback (most recent call last):
        ...
    ValueError: Input string does not match any known date format
    """

    dt = None
    formats = ["%d.%m", "%d.%m.%Y", "%Y-%m-%d"]
    for fmt in formats:
        try:
            dt = datetime.datetime.strptime(date_str, fmt)
            break
        except ValueError:
            pass
    if dt is None:
        raise ValueError("Input string does not match any known date format")
    return dt

def _format_recipients(recipients):
    """
    >>> _format_recipients(["herp"])
    u"herp's"

    >>> _format_recipients(["herp", "derp"])
    u"herp's and derp's"

    >>> _format_recipients(["herp", "derp", "durp"])
    u"herp's, derp's and durp's"
    """

    if not recipients:
        raise ValueError("Input list should contain at least one string")

    recs = ["{0}'s".format(recipient) for recipient in recipients[:]]
    if len(recs) is 1:
        return recs[0]
    elif len(recs) > 1:
        return "{0} and {1}".format(", ".join(recs[:-1]), recs[-1])

_timer = None

@atexit.register
def _cleanup():
    global _timer
    if _timer:
        _timer.cancel()
    _timer = None

class BirthdayReminder(Plugin):
    CHECK_INTERVAL = 600

    def __init__(self, priority=0, whitelist=None, **kwargs):
        super(BirthdayReminder, self).__init__(priority, whitelist, **kwargs)

        self.birthdays = dict()
        birthdays = self.options.setdefault("birthdays")
        for name, date_str in birthdays.iteritems():
            try:
                dt = str_to_datetime(date_str)
            except ValueError:
                self._logger.error("Parse error: '%s' for '%s'", name, date_str)
            else:
                self.birthdays.update({name: dt})

        if not self.birthdays:
            self._logger.warning("No valid dates has been parsed!")
            return

        self._notified = False
        self._repeats = 0
        self._repeat_timers = list()
        self._logger.info("Parsed %d date(s)", len(self.birthdays))
        self._check()

    def _check(self):
        global _timer
        if _timer is None:
            return
        today = datetime.datetime.today()
        recipients = list()
        for name, dt in self.birthdays.iteritems():
            dt = dt.replace(year=today.year)
            delta = today - dt
            if delta.days == 0:
                recipients.append(name)
        if recipients:
            if not self._notified:
                self._notify(recipients)
                self._notified = True
            return
        self._notified = False
        _timer = Timer(self.CHECK_INTERVAL, self._check)
        _timer.daemon = True
        _timer.start()

    def _notify(self, recipients):
        recs = _format_recipients(recipients)
        substitutes = {
            "recipient": recs,
            "interjection": random.choice(INTERJECTIONS),
        }
        msg = NOTIFICATION_TEMPLATE.format(**substitutes)

        for chat in self.whitelist:
            self.output.append(ChatMessage(chat, msg))

    def on_message_status(self, message, status):
        if status != cmsReceived:
            return

        if not message.Body.startswith("!birthdays"):
            return

        _pluralize = lambda n, s: "{0}s".format(s) if n > 1 else s

        last_deltas = list()
        next_deltas = list()
        todays_birthdays = list()
        today = datetime.datetime.today()
        for name, dt in self.birthdays.iteritems():
            dt = dt.replace(year=today.year)
            delta = today - dt
            item = (name, delta)
            if delta.days > 0:
                last_deltas.append(item)
            elif delta.days < 0:
                next_deltas.append(item)
            elif delta.days == 0:
                todays_birthdays.append(name)

        last_item = min(last_deltas, key=operator.itemgetter(1))
        last_recipient = _format_recipients((last_item[0],))
        last_delta = last_item[1].days
        next_item = max(next_deltas, key=operator.itemgetter(1))
        next_recipient = _format_recipients((next_item[0],))
        next_delta = abs(next_item[1].days)
        substitutes = {
            "last_recipient": last_recipient,
            "last_delta": last_delta,
            "last_day_or_days": _pluralize(last_delta, "day"),
            "next_recipient": next_recipient,
            "next_delta": next_delta,
            "next_day_or_days": _pluralize(next_delta, "day"),
        }
        msg = REMINDER_TEMPLATE.format(**substitutes)
        if todays_birthdays:
            recs = _format_recipients(todays_birthdays)
            substitutes = {
                "recipient": recs,
                "interjection": random.choice(INTERJECTIONS),
            }
            notification = NOTIFICATION_TEMPLATE.format(**substitutes)
            msg = "{0}{1}".format(notification, msg)

        self.output.append(ChatMessage(message.Chat.Name, msg))

        return message, status

if __name__ == "__main__":
    import doctest
    doctest.testmod()
<commit_msg>Threading fix<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
:mod:`birthdayreminder` --- Birthday reminder plugin
====================================================
"""

from __future__ import unicode_literals

__docformat__ = "restructuredtext en"

import atexit
import datetime
from threading import Timer
import random
import operator

from Skype4Py.enums import cmsReceived

from plugin import Plugin
from output import ChatMessage

NOTIFICATION_TEMPLATE = "Today is {recipient} birthday! {interjection}!"

REMINDER_TEMPLATE = """
It was {last_recipient} birthday {last_delta} {last_day_or_days} ago.
{next_recipient} birthday is coming up next in {next_delta} {next_day_or_days}.
"""

INTERJECTIONS = [
    "Hurrah",
    "Hooray",
    "Huzzah",
    "Fancy",
    "Awesome",
    "Whee",
    "Whoa",
    "Unbelievable",
    "Wow",
    "Amazing",
    "Yahoo",
    "Yippie",
    "Yay",
    "Yeah",
    "Exciting",
    "Yee-haw",
    "Sweet",
    "Fantastic",
    "OMG",
]

def str_to_datetime(date_str):
    """
    >>> str_to_datetime("13.1")
    datetime.datetime(1900, 1, 13, 0, 0)

    >>> str_to_datetime("13.01")
    datetime.datetime(1900, 1, 13, 0, 0)

    >>> str_to_datetime("01.13")
    Traceback (most recent call last):
        ...
    ValueError: Input string does not match any known date format

    >>> str_to_datetime("13.1.2000")
    datetime.datetime(2000, 1, 13, 0, 0)

    >>> str_to_datetime("13.01.2000")
    datetime.datetime(2000, 1, 13, 0, 0)

    >>> str_to_datetime("01.13.2000")
    Traceback (most recent call last):
        ...
    ValueError: Input string does not match any known date format
    """

    dt = None
    formats = ["%d.%m", "%d.%m.%Y", "%Y-%m-%d"]
    for fmt in formats:
        try:
            dt = datetime.datetime.strptime(date_str, fmt)
            break
        except ValueError:
            pass
    if dt is None:
        raise ValueError("Input string does not match any known date format")
    return dt

def _format_recipients(recipients):
    """
    >>> _format_recipients(["herp"])
    u"herp's"

    >>> _format_recipients(["herp", "derp"])
    u"herp's and derp's"

    >>> _format_recipients(["herp", "derp", "durp"])
    u"herp's, derp's and durp's"
    """

    if not recipients:
        raise ValueError("Input list should contain at least one string")

    recs = ["{0}'s".format(recipient) for recipient in recipients[:]]
    if len(recs) is 1:
        return recs[0]
    elif len(recs) > 1:
        return "{0} and {1}".format(", ".join(recs[:-1]), recs[-1])

_timer = ""

@atexit.register
def _cleanup():
    global _timer
    if _timer:
        _timer.cancel()
    _timer = None

class BirthdayReminder(Plugin):
    CHECK_INTERVAL = 600

    def __init__(self, priority=0, whitelist=None, **kwargs):
        super(BirthdayReminder, self).__init__(priority, whitelist, **kwargs)

        self.birthdays = dict()
        birthdays = self.options.setdefault("birthdays")
        for name, date_str in birthdays.iteritems():
            try:
                dt = str_to_datetime(date_str)
            except ValueError:
                self._logger.error("Parse error: '%s' for '%s'", name, date_str)
            else:
                self.birthdays.update({name: dt})

        if not self.birthdays:
            self._logger.warning("No valid dates has been parsed!")
            return

        self._notified = False
        self._repeats = 0
        self._repeat_timers = list()
        self._logger.info("Parsed %d date(s)", len(self.birthdays))
        self._check()

    def _check(self):
        global _timer
        if _timer is None:
            return
        today = datetime.datetime.today()
        recipients = list()
        for name, dt in self.birthdays.iteritems():
            dt = dt.replace(year=today.year)
            delta = today - dt
            if delta.days == 0:
                recipients.append(name)
        if recipients:
            if not self._notified:
                self._notify(recipients)
                self._notified = True
            return
        self._notified = False
        _timer = Timer(self.CHECK_INTERVAL, self._check)
        _timer.daemon = True
        _timer.start()

    def _notify(self, recipients):
        recs = _format_recipients(recipients)
        substitutes = {
            "recipient": recs,
            "interjection": random.choice(INTERJECTIONS),
        }
        msg = NOTIFICATION_TEMPLATE.format(**substitutes)

        for chat in self.whitelist:
            self.output.append(ChatMessage(chat, msg))

    def on_message_status(self, message, status):
        if status != cmsReceived:
            return

        if not message.Body.startswith("!birthdays"):
            return

        _pluralize = lambda n, s: "{0}s".format(s) if n > 1 else s

        last_deltas = list()
        next_deltas = list()
        todays_birthdays = list()
        today = datetime.datetime.today()
        for name, dt in self.birthdays.iteritems():
            dt = dt.replace(year=today.year)
            delta = today - dt
            item = (name, delta)
            if delta.days > 0:
                last_deltas.append(item)
            elif delta.days < 0:
                next_deltas.append(item)
            elif delta.days == 0:
                todays_birthdays.append(name)

        last_item = min(last_deltas, key=operator.itemgetter(1))
        last_recipient = _format_recipients((last_item[0],))
        last_delta = last_item[1].days
        next_item = max(next_deltas, key=operator.itemgetter(1))
        next_recipient = _format_recipients((next_item[0],))
        next_delta = abs(next_item[1].days)
        substitutes = {
            "last_recipient": last_recipient,
            "last_delta": last_delta,
            "last_day_or_days": _pluralize(last_delta, "day"),
            "next_recipient": next_recipient,
            "next_delta": next_delta,
            "next_day_or_days": _pluralize(next_delta, "day"),
        }
        msg = REMINDER_TEMPLATE.format(**substitutes)
        if todays_birthdays:
            recs = _format_recipients(todays_birthdays)
            substitutes = {
                "recipient": recs,
                "interjection": random.choice(INTERJECTIONS),
            }
            notification = NOTIFICATION_TEMPLATE.format(**substitutes)
            msg = "{0}{1}".format(notification, msg)

        self.output.append(ChatMessage(message.Chat.Name, msg))

        return message, status

if __name__ == "__main__":
    import doctest
    doctest.testmod()
<|endoftext|>


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

# This is a python script that emulates a terminal session and runs
# commands from a supplied markdown file..

import difflib
import optparse
import os
import pexpect
import random
import time
import shlex
import sys
import json
from colorama import init
from colorama import Fore, Style
init()

def type_command(command, script_dir, simulation):
    # Displays the command on the screen
    # If simulation == True then it will look like someone is typing the command
    print(Fore.GREEN+Style.BRIGHT)
    for char in command:
        if (char != '\n'):
            print(char, end="", flush=True)
        if simulation:
            delay = random.uniform(0.01, 0.04)
            time.sleep(delay)
    print(Style.RESET_ALL)

def simulate_command(command, script_dir, env = None, simulation = True, is_automatic=False):
    # Types the command on the screen, executes it and outputs the
    # results if simulation == True then system will make the "typing"
    # look real and will wait for keyboard entry before proceeding to
    # the next command
    type_command(command, script_dir, simulation)
    check_for_interactive_command(script_dir, is_automatic)
    print()
    output = run_command(command, script_dir, env)

    return output

def get_simdem_environment(directory):
    # Populates each shell environment with a set of environment vars
    # loaded via env.json file stored either in the project root
    # directory

    env = {}

    if not directory.endswith('/'):
        directory = directory + "/"

    filename = directory + "../env.json"
    if os.path.isfile(directory + "../env.json"):
        with open(filename) as env_file:
            app_env = json.load(env_file)
            env.update(app_env)

    filename = directory + "env.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            script_env = json.load(env_file)
            env.update(script_env)

    filename = directory + "../env.local.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            local_env = json.load(env_file)
            env.update(local_env)

    filename = directory + "env.local.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            local_env = json.load(env_file)
            env.update(local_env)

    return env

def run_command(command, script_dir, env=None):
    if command.startswith("sudo "):
        is_docker_command = 'if [ -f /.dockerenv ]; then echo "True"; else echo "False"; fi'
        shell = pexpect.spawnu('/bin/bash', ['-c', is_docker_command], env=env, cwd=script_dir, timeout=None)
        shell.expect(pexpect.EOF)
        is_docker = shell.before.strip() == "True"
        if is_docker:
            command = command[5:]

    shell = pexpect.spawnu('/bin/bash', ['-c', command], env=env, cwd=script_dir, timeout=None)
    shell.logfile = sys.stdout
    shell.expect(pexpect.EOF)
    return shell.before

def check_for_interactive_command(script_dir, is_automated=False):
    # Wait for a key to be pressed. Most keys result in the script
    # progressing, but a few have special meaning. See the
    # documentation or code for a description of the special keys.
    if not is_automated:
        key = get_instruction_key()

        if key == 'b' or key == 'B':
            command = input()
            run_command(command, script_dir)
            print("$ ", end="", flush=True)
            check_for_interactive_command(script_dir, is_automated)

def get_instruction_key():
    """Waits for a single keypress on stdin.

    This is a silly function to call if you need to do it a lot because it has
    to store stdin's current setup, setup stdin for reading single keystrokes
    then read the single keystroke then revert stdin back after reading the
    keystroke.

    Returns the character of the key that was pressed (zero on
    KeyboardInterrupt which can happen when a signal gets handled)

    This method is licensed under cc by-sa 3.0
    Thanks to mheyman http://stackoverflow.com/questions/983354/how-do-i-make-python-to-wait-for-a-pressed-key\
    """
    import termios, fcntl, sys, os
    fd = sys.stdin.fileno()
    # save old state
    flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
    attrs_save = termios.tcgetattr(fd)
    # make raw - the way to do this comes from the termios(3) man page.
    attrs = list(attrs_save) # copy the stored version to update
    # iflag
    attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
                  | termios.ISTRIP | termios.INLCR | termios. IGNCR
                  | termios.ICRNL | termios.IXON )
    # oflag
    attrs[1] &= ~termios.OPOST
    # cflag
    attrs[2] &= ~(termios.CSIZE | termios. PARENB)
    attrs[2] |= termios.CS8
    # lflag
    attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
                  | termios.ISIG | termios.IEXTEN)
    termios.tcsetattr(fd, termios.TCSANOW, attrs)
    # turn off non-blocking
    fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
    # read a single keystroke
    try:
        ret = sys.stdin.read(1) # returns a single character
    except KeyboardInterrupt:
        ret = 0
    finally:
        # restore old state
        termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
    return ret

def test_results(expected_results, actual_results, expected_similarity = 0.66):
    differ = difflib.Differ()
    comparison = differ.compare(actual_results, expected_results)
    diff = differ.compare(actual_results, expected_results)
    seq = difflib.SequenceMatcher(lambda x: x in " \t\n\r", actual_results, expected_results)

    is_pass = seq.ratio() >= expected_similarity

    if not is_pass:
        print("\n\n=============================\n\n")
        print("FAILED")
        print("Similarity ratio:    " + str(seq.ratio()))
        print("Expected Similarity: " + str(expected_similarity))
        print("\n\n=============================\n\n")
        print("Expected results:")
        print(expected_results)
        print("Actual results:")
        print(actual_results)
        print("\n\n=============================\n\n")

    return is_pass

def run_script(script_dir, env=None, is_simulation = True, is_automated=False, is_testing=False):
    # Reads a script.md file in the indicated directoy and runs the
    # commands contained within. If simulation == True then human
    # entry will be simulated (looks like typing and waits for
    # keyboard input before proceeding to the next command). This is
    # useful if you want to run a fully automated demo.
    #
    # The script.md file will be parsed as follows:
    #
    # ``` marks the start or end of a code block
    #
    # Each line in a code block will be treated as a separate command.
    #
    # All other lines will be ignored

    in_code_block = False
    in_results_section = False

    if not script_dir.endswith('/'):
        script_dir = script_dir + "/"
    filename = script_dir + "script.md"

    lines = list(open(filename))
    expected_results = ""
    actual_results = ""
    passed_tests = 0
    failed_tests = 0
    is_first_line = True
    executed_code_in_this_section = False

    for line in lines:
        if in_results_section and in_code_block and not line.startswith("```"):
            expected_results += line

        if line.startswith("Results:"):
            # Entering results section
            in_results_section = True
            pos = line.lower().find("expected similarity: ")
            if pos >= 0:
                pos = pos + len("expected similarity: ")
                s = line[pos:]
                expected_similarity = float(s)
            else:
                expected_similarity = 0.66
        elif line.startswith("```") and not in_code_block:
            # Entering a code block, if in_results_section = True then it's a results block
            in_code_block = True
        elif line.startswith("```") and in_code_block and in_results_section:
            # Finishing results section
            if in_results_section and is_testing:
                if test_results(expected_results, actual_results, expected_similarity):
                    passed_tests += 1
                else:
                    failed_tests += 1
            expected_results = ""
            actual_results = ""
            in_results_section = False
            in_code_block = False
        elif line.startswith("```") and in_code_block and not in_results_section:
            # Finishing executable code block
            in_code_block = False
        elif in_code_block and not in_results_section:
            # Executable line
            print("$ ", end="", flush=True)
            check_for_interactive_command(script_dir, is_automated)
            actual_results = simulate_command(line, script_dir, env, is_simulation, is_automated)
            executed_code_in_this_section = True
        elif line.startswith("#") and not in_code_block and not in_results_section and not is_automated:
            # Heading in descriptive text, indicating a new section
            if is_first_line:
                run_command("clear", script_dir, env)
            elif executed_code_in_this_section:
                executed_code_in_this_section = False
                print("$ ", end="", flush=True)
                check_for_interactive_command(script_dir, is_automated)
                simulate_command("clear", script_dir, env, is_simulation, is_automated)
                if not is_simulation:
                    print("$ ", end="", flush=True)
                    # Since this is a heading we are not really simulating a command, it appears as a comment
                    simulate_command(line, script_dir, env, is_simulation, is_automated)
        elif not is_simulation and not in_results_section:
            # Descriptinve text
            print(line, end="", flush=True)

        is_first_line = False

    if is_testing:
        print("\n\n=============================\n\n")
        print("Test Run Complete.")
        print("Failed Tests: " + str(failed_tests))
        print("Passed Tests: " + str(passed_tests))
        if failed_tests > 0:
            print("\n\n")
            print("View failure reports in context in the above output.")
        print("\n\n=============================\n\n")

def get_bash_script(script_dir, env=None, is_simulation = True, is_automated=False, is_testing=False):
    # Reads a script.md file in the indicated directoy and builds an
    # executable bash script from the commands contained within.

    in_code_block = False
    in_results_section = False

    if not script_dir.endswith('/'):
        script_dir = script_dir + "/"
    filename = script_dir + "script.md"

    script = ""
    env = get_simdem_environment(script_dir)
    for key, value in env.items():
        script += key + "='" + value + "'\n"

    lines = list(open(filename))
    for line in lines:
        if line.startswith("Results:"):
            # Entering results section
            in_results_section = True
        elif line.startswith("```") and not in_code_block:
            # Entering a code block, if in_results_section = True then it's a results block
            in_code_block = True
        elif line.startswith("```") and in_code_block and in_results_section:
            # Finishing results section
            in_results_section = False
            in_code_block = False
        elif line.startswith("```") and in_code_block and not in_results_section:
            # Finishing executable code block
            in_code_block = False
        elif in_code_block and not in_results_section:
            # Executable line
            script += line
        elif line.startswith("#") and not in_code_block and not in_results_section and not is_automated:
            # Heading in descriptive text
            script +="\n"
    return script

def main():
    """SimDem CLI interpreter"""

    p = optparse.OptionParser("%prog [run|test|script] <options> DEMO_NAME", version="%prog 0.2.3")
    p.add_option('--style', '-s', default="tutorial",
                 help="The style of simulation you want to run. 'tutorial' (the default) will print out all text and pause for user input before running commands. 'simulate' will not print out the text but will still pause for input.")
    p.add_option('--path', '-p', default="demo_scripts/",
                 help="The Path to the demo scripts directory.")
    p.add_option('--auto', '-a', default="False",
                 help="If set to anything other than False the application will not wait for user keypresses between commands.")
    p.add_option('--test', '-t', default="False",
                 help="If set to anything other than False the output of the command will be compared to the expected results in the sript. Any failures will be reported")

    options, arguments = p.parse_args()

    if len(arguments) == 0:
        arguments.append("run")

    if not options.path.endswith("/"):
        options.path += "/"

    if options.auto == "False":
        is_automatic = False
    else:
        is_automatic = True

    if options.test == "False":
        is_test = False
    else:
        is_test = True

    if options.style == "simulate":
        simulate = True
    elif options.style == 'tutorial':
        simulate = False
    else:
        print("Unkown style (--style, -s): " + options.style)
        exit(1)

    if len(arguments) == 2:
        script_dir = options.path + arguments[1]
    else:
        script_dir = options.path

    env = os.environ.copy()
    env.update(get_simdem_environment(script_dir))
    cmd = arguments[0]

    if cmd == "run":
        run_script(script_dir, env, simulate, is_automatic, is_test)
    elif cmd == "test":
        is_automatic = True and options.auto
        is_test = True and options.test
        run_script(script_dir, env, simulate, is_automatic, is_test)
    elif cmd == "script":
        print(get_bash_script(script_dir, env))
    else:
        print("Unknown command: " + cmd)
        print("Run with --help for guidance.")
main()
<commit_msg>Make typed text white to differentiate from result and descriptive text. Don't import specific part sof Colorama (I find it makes the code more readable, especially for the init() method - init what?)<commit_after>#!/usr/bin/env python3

# This is a python script that emulates a terminal session and runs
# commands from a supplied markdown file..

import difflib
import optparse
import os
import pexpect
import random
import time
import shlex
import sys
import json
import colorama
colorama.init(strip=None)

def type_command(command, script_dir, simulation):
    # Displays the command on the screen
    # If simulation == True then it will look like someone is typing the command
    print(colorama.Fore.WHITE+colorama.Style.BRIGHT, end="")
    for char in command:
        if (char != '\n'):
            print(char, end="", flush=True)
        if simulation:
            delay = random.uniform(0.01, 0.04)
            time.sleep(delay)
    print(colorama.Style.RESET_ALL, end="")

def simulate_command(command, script_dir, env = None, simulation = True, is_automatic=False):
    # Types the command on the screen, executes it and outputs the
    # results if simulation == True then system will make the "typing"
    # look real and will wait for keyboard entry before proceeding to
    # the next command
    type_command(command, script_dir, simulation)
    check_for_interactive_command(script_dir, is_automatic)
    print()
    output = run_command(command, script_dir, env)

    return output

def get_simdem_environment(directory):
    # Populates each shell environment with a set of environment vars
    # loaded via env.json file stored either in the project root
    # directory

    env = {}

    if not directory.endswith('/'):
        directory = directory + "/"

    filename = directory + "../env.json"
    if os.path.isfile(directory + "../env.json"):
        with open(filename) as env_file:
            app_env = json.load(env_file)
            env.update(app_env)

    filename = directory + "env.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            script_env = json.load(env_file)
            env.update(script_env)

    filename = directory + "../env.local.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            local_env = json.load(env_file)
            env.update(local_env)

    filename = directory + "env.local.json"
    if os.path.isfile(filename):
        with open(filename) as env_file:
            local_env = json.load(env_file)
            env.update(local_env)

    return env

def run_command(command, script_dir, env=None):
    if command.startswith("sudo "):
        is_docker_command = 'if [ -f /.dockerenv ]; then echo "True"; else echo "False"; fi'
        shell = pexpect.spawnu('/bin/bash', ['-c', is_docker_command], env=env, cwd=script_dir, timeout=None)
        shell.expect(pexpect.EOF)
        is_docker = shell.before.strip() == "True"
        if is_docker:
            command = command[5:]

    print(colorama.Fore.GREEN+colorama.Style.BRIGHT)
    shell = pexpect.spawnu('/bin/bash', ['-c', command], env=env, cwd=script_dir, timeout=None)
    shell.logfile = sys.stdout
    shell.expect(pexpect.EOF)
    print(colorama.Style.RESET_ALL)
    return shell.before

def check_for_interactive_command(script_dir, is_automated=False):
    # Wait for a key to be pressed. Most keys result in the script
    # progressing, but a few have special meaning. See the
    # documentation or code for a description of the special keys.
    if not is_automated:
        key = get_instruction_key()

        if key == 'b' or key == 'B':
            command = input()
            run_command(command, script_dir)
            print("$ ", end="", flush=True)
            check_for_interactive_command(script_dir, is_automated)

def get_instruction_key():
    """Waits for a single keypress on stdin.

    This is a silly function to call if you need to do it a lot because it has
    to store stdin's current setup, setup stdin for reading single keystrokes
    then read the single keystroke then revert stdin back after reading the
    keystroke.

    Returns the character of the key that was pressed (zero on
    KeyboardInterrupt which can happen when a signal gets handled)

    This method is licensed under cc by-sa 3.0
    Thanks to mheyman http://stackoverflow.com/questions/983354/how-do-i-make-python-to-wait-for-a-pressed-key\
    """
    import termios, fcntl, sys, os
    fd = sys.stdin.fileno()
    # save old state
    flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
    attrs_save = termios.tcgetattr(fd)
    # make raw - the way to do this comes from the termios(3) man page.
    attrs = list(attrs_save) # copy the stored version to update
    # iflag
    attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
                  | termios.ISTRIP | termios.INLCR | termios. IGNCR
                  | termios.ICRNL | termios.IXON )
    # oflag
    attrs[1] &= ~termios.OPOST
    # cflag
    attrs[2] &= ~(termios.CSIZE | termios. PARENB)
    attrs[2] |= termios.CS8
    # lflag
    attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
                  | termios.ISIG | termios.IEXTEN)
    termios.tcsetattr(fd, termios.TCSANOW, attrs)
    # turn off non-blocking
    fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
    # read a single keystroke
    try:
        ret = sys.stdin.read(1) # returns a single character
    except KeyboardInterrupt:
        ret = 0
    finally:
        # restore old state
        termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
    return ret

def test_results(expected_results, actual_results, expected_similarity = 0.66):
    differ = difflib.Differ()
    comparison = differ.compare(actual_results, expected_results)
    diff = differ.compare(actual_results, expected_results)
    seq = difflib.SequenceMatcher(lambda x: x in " \t\n\r", actual_results, expected_results)

    is_pass = seq.ratio() >= expected_similarity

    if not is_pass:
        print("\n\n=============================\n\n")
        print("FAILED")
        print("Similarity ratio:    " + str(seq.ratio()))
        print("Expected Similarity: " + str(expected_similarity))
        print("\n\n=============================\n\n")
        print("Expected results:")
        print(expected_results)
        print("Actual results:")
        print(actual_results)
        print("\n\n=============================\n\n")

    return is_pass

def run_script(script_dir, env=None, is_simulation = True, is_automated=False, is_testing=False):
    # Reads a script.md file in the indicated directoy and runs the
    # commands contained within. If simulation == True then human
    # entry will be simulated (looks like typing and waits for
    # keyboard input before proceeding to the next command). This is
    # useful if you want to run a fully automated demo.
    #
    # The script.md file will be parsed as follows:
    #
    # ``` marks the start or end of a code block
    #
    # Each line in a code block will be treated as a separate command.
    #
    # All other lines will be ignored

    in_code_block = False
    in_results_section = False

    if not script_dir.endswith('/'):
        script_dir = script_dir + "/"
    filename = script_dir + "script.md"

    lines = list(open(filename))
    expected_results = ""
    actual_results = ""
    passed_tests = 0
    failed_tests = 0
    is_first_line = True
    executed_code_in_this_section = False

    for line in lines:
        if in_results_section and in_code_block and not line.startswith("```"):
            expected_results += line

        if line.startswith("Results:"):
            # Entering results section
            in_results_section = True
            pos = line.lower().find("expected similarity: ")
            if pos >= 0:
                pos = pos + len("expected similarity: ")
                s = line[pos:]
                expected_similarity = float(s)
            else:
                expected_similarity = 0.66
        elif line.startswith("```") and not in_code_block:
            # Entering a code block, if in_results_section = True then it's a results block
            in_code_block = True
        elif line.startswith("```") and in_code_block and in_results_section:
            # Finishing results section
            if in_results_section and is_testing:
                if test_results(expected_results, actual_results, expected_similarity):
                    passed_tests += 1
                else:
                    failed_tests += 1
            expected_results = ""
            actual_results = ""
            in_results_section = False
            in_code_block = False
        elif line.startswith("```") and in_code_block and not in_results_section:
            # Finishing executable code block
            in_code_block = False
        elif in_code_block and not in_results_section:
            # Executable line
            print("$ ", end="", flush=True)
            check_for_interactive_command(script_dir, is_automated)
            actual_results = simulate_command(line, script_dir, env, is_simulation, is_automated)
            executed_code_in_this_section = True
        elif line.startswith("#") and not in_code_block and not in_results_section and not is_automated:
            # Heading in descriptive text, indicating a new section
            if is_first_line:
                run_command("clear", script_dir, env)
            elif executed_code_in_this_section:
                executed_code_in_this_section = False
                print("$ ", end="", flush=True)
                check_for_interactive_command(script_dir, is_automated)
                simulate_command("clear", script_dir, env, is_simulation, is_automated)
                if not is_simulation:
                    print("$ ", end="", flush=True)
                    # Since this is a heading we are not really simulating a command, it appears as a comment
                    simulate_command(line, script_dir, env, is_simulation, is_automated)
        elif not is_simulation and not in_results_section:
            # Descriptinve text
            print(line, end="", flush=True)

        is_first_line = False

    if is_testing:
        print("\n\n=============================\n\n")
        print("Test Run Complete.")
        print("Failed Tests: " + str(failed_tests))
        print("Passed Tests: " + str(passed_tests))
        if failed_tests > 0:
            print("\n\n")
            print("View failure reports in context in the above output.")
        print("\n\n=============================\n\n")

def get_bash_script(script_dir, env=None, is_simulation = True, is_automated=False, is_testing=False):
    # Reads a script.md file in the indicated directoy and builds an
    # executable bash script from the commands contained within.

    in_code_block = False
    in_results_section = False

    if not script_dir.endswith('/'):
        script_dir = script_dir + "/"
    filename = script_dir + "script.md"

    script = ""
    env = get_simdem_environment(script_dir)
    for key, value in env.items():
        script += key + "='" + value + "'\n"

    lines = list(open(filename))
    for line in lines:
        if line.startswith("Results:"):
            # Entering results section
            in_results_section = True
        elif line.startswith("```") and not in_code_block:
            # Entering a code block, if in_results_section = True then it's a results block
            in_code_block = True
        elif line.startswith("```") and in_code_block and in_results_section:
            # Finishing results section
            in_results_section = False
            in_code_block = False
        elif line.startswith("```") and in_code_block and not in_results_section:
            # Finishing executable code block
            in_code_block = False
        elif in_code_block and not in_results_section:
            # Executable line
            script += line
        elif line.startswith("#") and not in_code_block and not in_results_section and not is_automated:
            # Heading in descriptive text
            script +="\n"
    return script

def main():
    """SimDem CLI interpreter"""

    p = optparse.OptionParser("%prog [run|test|script] <options> DEMO_NAME", version="%prog 0.2.3")
    p.add_option('--style', '-s', default="tutorial",
                 help="The style of simulation you want to run. 'tutorial' (the default) will print out all text and pause for user input before running commands. 'simulate' will not print out the text but will still pause for input.")
    p.add_option('--path', '-p', default="demo_scripts/",
                 help="The Path to the demo scripts directory.")
    p.add_option('--auto', '-a', default="False",
                 help="If set to anything other than False the application will not wait for user keypresses between commands.")
    p.add_option('--test', '-t', default="False",
                 help="If set to anything other than False the output of the command will be compared to the expected results in the sript. Any failures will be reported")

    options, arguments = p.parse_args()

    if len(arguments) == 0:
        arguments.append("run")

    if not options.path.endswith("/"):
        options.path += "/"

    if options.auto == "False":
        is_automatic = False
    else:
        is_automatic = True

    if options.test == "False":
        is_test = False
    else:
        is_test = True

    if options.style == "simulate":
        simulate = True
    elif options.style == 'tutorial':
        simulate = False
    else:
        print("Unkown style (--style, -s): " + options.style)
        exit(1)

    if len(arguments) == 2:
        script_dir = options.path + arguments[1]
    else:
        script_dir = options.path

    env = os.environ.copy()
    env.update(get_simdem_environment(script_dir))
    cmd = arguments[0]

    if cmd == "run":
        run_script(script_dir, env, simulate, is_automatic, is_test)
    elif cmd == "test":
        is_automatic = True and options.auto
        is_test = True and options.test
        run_script(script_dir, env, simulate, is_automatic, is_test)
    elif cmd == "script":
        print(get_bash_script(script_dir, env))
    else:
        print("Unknown command: " + cmd)
        print("Run with --help for guidance.")
main()
<|endoftext|>


<commit_before>"""Module that groups code required to handle state restore for component."""
from __future__ import annotations

import asyncio
from collections.abc import Iterable
from typing import Any

from homeassistant.const import (
    ATTR_SUPPORTED_FEATURES,
    SERVICE_MEDIA_PAUSE,
    SERVICE_MEDIA_PLAY,
    SERVICE_MEDIA_STOP,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    SERVICE_VOLUME_MUTE,
    SERVICE_VOLUME_SET,
    STATE_IDLE,
    STATE_OFF,
    STATE_ON,
    STATE_PAUSED,
    STATE_PLAYING,
)
from homeassistant.core import Context, HomeAssistant, State

from .const import (
    ATTR_INPUT_SOURCE,
    ATTR_MEDIA_CONTENT_ID,
    ATTR_MEDIA_CONTENT_TYPE,
    ATTR_MEDIA_ENQUEUE,
    ATTR_MEDIA_VOLUME_LEVEL,
    ATTR_MEDIA_VOLUME_MUTED,
    ATTR_SOUND_MODE,
    DOMAIN,
    SERVICE_PLAY_MEDIA,
    SERVICE_SELECT_SOUND_MODE,
    SERVICE_SELECT_SOURCE,
    MediaPlayerEntityFeature,
)

# mypy: allow-untyped-defs

async def _async_reproduce_states(
    hass: HomeAssistant,
    state: State,
    *,
    context: Context | None = None,
    reproduce_options: dict[str, Any] | None = None,
) -> None:
    """Reproduce component states."""
    cur_state = hass.states.get(state.entity_id)
    features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0

    async def call_service(service: str, keys: Iterable) -> None:
        """Call service with set of attributes given."""
        data = {"entity_id": state.entity_id}
        for key in keys:
            if key in state.attributes:
                data[key] = state.attributes[key]

        await hass.services.async_call(
            DOMAIN, service, data, blocking=True, context=context
        )

    if state.state == STATE_OFF:
        if features & MediaPlayerEntityFeature.TURN_OFF:
            await call_service(SERVICE_TURN_OFF, [])
        # entities that are off have no other attributes to restore
        return

    if (
        state.state
        in (
            STATE_ON,
            STATE_PLAYING,
            STATE_IDLE,
            STATE_PAUSED,
        )
        and features & MediaPlayerEntityFeature.TURN_ON
    ):
        await call_service(SERVICE_TURN_ON, [])

    cur_state = hass.states.get(state.entity_id)
    features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0

    if (
        ATTR_MEDIA_VOLUME_LEVEL in state.attributes
        and features & MediaPlayerEntityFeature.VOLUME_SET
    ):
        await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL])

    if (
        ATTR_MEDIA_VOLUME_MUTED in state.attributes
        and features & MediaPlayerEntityFeature.VOLUME_MUTE
    ):
        await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED])

    if (
        ATTR_INPUT_SOURCE in state.attributes
        and features & MediaPlayerEntityFeature.SELECT_SOURCE
    ):
        await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE])

    if (
        ATTR_SOUND_MODE in state.attributes
        and features & MediaPlayerEntityFeature.SELECT_SOUND_MODE
    ):
        await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE])

    already_playing = False

    if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and (
        ATTR_MEDIA_CONTENT_ID in state.attributes
    ):
        if features & MediaPlayerEntityFeature.PLAY_MEDIA:
            await call_service(
                SERVICE_PLAY_MEDIA,
                [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE],
            )
        already_playing = True

    if (
        not already_playing
        and state.state == STATE_PLAYING
        and features & MediaPlayerEntityFeature.PLAY
    ):
        await call_service(SERVICE_MEDIA_PLAY, [])
    elif state.state == STATE_IDLE:
        if features & MediaPlayerEntityFeature.STOP:
            await call_service(SERVICE_MEDIA_STOP, [])
    elif state.state == STATE_PAUSED:
        if features & MediaPlayerEntityFeature.PAUSE:
            await call_service(SERVICE_MEDIA_PAUSE, [])

async def async_reproduce_states(
    hass: HomeAssistant,
    states: Iterable[State],
    *,
    context: Context | None = None,
    reproduce_options: dict[str, Any] | None = None,
) -> None:
    """Reproduce component states."""
    await asyncio.gather(
        *(
            _async_reproduce_states(
                hass, state, context=context, reproduce_options=reproduce_options
            )
            for state in states
        )
    )
<commit_msg>Set source & sound mode at start in media player reproduce state (#70064)<commit_after>"""Module that groups code required to handle state restore for component."""
from __future__ import annotations

import asyncio
from collections.abc import Iterable
from typing import Any

from homeassistant.const import (
    ATTR_SUPPORTED_FEATURES,
    SERVICE_MEDIA_PAUSE,
    SERVICE_MEDIA_PLAY,
    SERVICE_MEDIA_STOP,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    SERVICE_VOLUME_MUTE,
    SERVICE_VOLUME_SET,
    STATE_IDLE,
    STATE_OFF,
    STATE_ON,
    STATE_PAUSED,
    STATE_PLAYING,
)
from homeassistant.core import Context, HomeAssistant, State

from .const import (
    ATTR_INPUT_SOURCE,
    ATTR_MEDIA_CONTENT_ID,
    ATTR_MEDIA_CONTENT_TYPE,
    ATTR_MEDIA_ENQUEUE,
    ATTR_MEDIA_VOLUME_LEVEL,
    ATTR_MEDIA_VOLUME_MUTED,
    ATTR_SOUND_MODE,
    DOMAIN,
    SERVICE_PLAY_MEDIA,
    SERVICE_SELECT_SOUND_MODE,
    SERVICE_SELECT_SOURCE,
    MediaPlayerEntityFeature,
)

# mypy: allow-untyped-defs

async def _async_reproduce_states(
    hass: HomeAssistant,
    state: State,
    *,
    context: Context | None = None,
    reproduce_options: dict[str, Any] | None = None,
) -> None:
    """Reproduce component states."""
    cur_state = hass.states.get(state.entity_id)
    features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0

    async def call_service(service: str, keys: Iterable) -> None:
        """Call service with set of attributes given."""
        data = {"entity_id": state.entity_id}
        for key in keys:
            if key in state.attributes:
                data[key] = state.attributes[key]

        await hass.services.async_call(
            DOMAIN, service, data, blocking=True, context=context
        )

    if state.state == STATE_OFF:
        if features & MediaPlayerEntityFeature.TURN_OFF:
            await call_service(SERVICE_TURN_OFF, [])
        # entities that are off have no other attributes to restore
        return

    if (
        state.state
        in (
            STATE_ON,
            STATE_PLAYING,
            STATE_IDLE,
            STATE_PAUSED,
        )
        and features & MediaPlayerEntityFeature.TURN_ON
    ):
        await call_service(SERVICE_TURN_ON, [])

    cur_state = hass.states.get(state.entity_id)
    features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0

    # First set source & sound mode to match the saved supported features
    if (
        ATTR_INPUT_SOURCE in state.attributes
        and features & MediaPlayerEntityFeature.SELECT_SOURCE
    ):
        await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE])

    if (
        ATTR_SOUND_MODE in state.attributes
        and features & MediaPlayerEntityFeature.SELECT_SOUND_MODE
    ):
        await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE])

    if (
        ATTR_MEDIA_VOLUME_LEVEL in state.attributes
        and features & MediaPlayerEntityFeature.VOLUME_SET
    ):
        await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL])

    if (
        ATTR_MEDIA_VOLUME_MUTED in state.attributes
        and features & MediaPlayerEntityFeature.VOLUME_MUTE
    ):
        await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED])

    already_playing = False

    if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and (
        ATTR_MEDIA_CONTENT_ID in state.attributes
    ):
        if features & MediaPlayerEntityFeature.PLAY_MEDIA:
            await call_service(
                SERVICE_PLAY_MEDIA,
                [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE],
            )
        already_playing = True

    if (
        not already_playing
        and state.state == STATE_PLAYING
        and features & MediaPlayerEntityFeature.PLAY
    ):
        await call_service(SERVICE_MEDIA_PLAY, [])
    elif state.state == STATE_IDLE:
        if features & MediaPlayerEntityFeature.STOP:
            await call_service(SERVICE_MEDIA_STOP, [])
    elif state.state == STATE_PAUSED:
        if features & MediaPlayerEntityFeature.PAUSE:
            await call_service(SERVICE_MEDIA_PAUSE, [])

async def async_reproduce_states(
    hass: HomeAssistant,
    states: Iterable[State],
    *,
    context: Context | None = None,
    reproduce_options: dict[str, Any] | None = None,
) -> None:
    """Reproduce component states."""
    await asyncio.gather(
        *(
            _async_reproduce_states(
                hass, state, context=context, reproduce_options=reproduce_options
            )
            for state in states
        )
    )
<|endoftext|>


<commit_before>from functools import wraps
from flask import render_template, request, Flask, flash, redirect, url_for, abort, jsonify, Response, make_response
import re
from unicodedata import normalize
from flask.ext.sqlalchemy import SQLAlchemy
import datetime
import markdown
from werkzeug.security import check_password_hash

app = Flask(__name__)
app.config.from_object('settings')
db = SQLAlchemy(app)

_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')

class Post(db.Model):
    __tablename__ = "posts"
    id    = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String())
    slug  = db.Column(db.String(), unique=True)
    text  = db.Column(db.String(), default="")
    draft = db.Column(db.Boolean(), index=True, default=True)
    views = db.Column(db.Integer(), default=0)
    created_at = db.Column(db.DateTime, index=True)
    updated_at = db.Column(db.DateTime)

    def render_content(self):
        return markdown.Markdown(extensions=['fenced_code'], output_format="html5", safe_mode=True).convert(self.text)

try:
    db.create_all()
except Exception:
    pass

def is_admin():
    auth = request.authorization
    if not auth or not (auth.username == app.config["ADMIN_USERNAME"]
                        and check_password_hash(app.config["ADMIN_PASSWORD"], auth.password)):
        return False
    return True

def requires_authentication(f):
    @wraps(f)
    def _auth_decorator(*args, **kwargs):
        if not is_admin():
            return Response("Could not authenticate you", 401, {"WWW-Authenticate":'Basic realm="Login Required"'})
        return f(*args, **kwargs)

    return _auth_decorator

@app.route("/")
def index():
    page = request.args.get("page", 0, type=int)
    posts_master = db.session.query(Post).filter_by(draft=False).order_by(Post.created_at.desc())
    posts_count = posts_master.count()

    posts = posts_master.limit(app.config["POSTS_PER_PAGE"]).offset(page*app.config["POSTS_PER_PAGE"]).all()
    is_more = posts_count > ((page*app.config["POSTS_PER_PAGE"]) + app.config["POSTS_PER_PAGE"])

    return render_template("index.html", posts=posts, now=datetime.datetime.now(),
                                         is_more=is_more, current_page=page, is_admin=is_admin())

@app.route("/<int:post_id>")
def view_post(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id, draft=False).one()
    except Exception:
        return abort(404)

    db.session.query(Post).filter_by(id=post_id).update({Post.views:Post.views+1})
    db.session.commit()

    return render_template("view.html", post=post, is_admin=is_admin())

@app.route("/<slug>")
def view_post_slug(slug):
    try:
        post = db.session.query(Post).filter_by(slug=slug,draft=False).one()
    except Exception:
        return abort(404)

    if not any(botname in request.user_agent.string for botname in ['Googlebot','Slurp','Twiceler','msnbot',
                                                                    'KaloogaBot','YodaoBot','"Baiduspider',
                                                                    'googlebot','Speedy Spider','DotBot']):
        db.session.query(Post).filter_by(slug=slug).update({Post.views:Post.views+1})
        db.session.commit()

    pid = request.args.get("pid", "0")
    return render_template("view.html", post=post, pid=pid, is_admin=is_admin())

@app.route("/new", methods=["POST", "GET"])
@requires_authentication
def new_post():
    post = Post()
    post.title = request.form.get("title","untitled")
    post.slug = slugify(post.title)
    post.created_at = datetime.datetime.now()
    post.updated_at = datetime.datetime.now()

    db.session.add(post)
    db.session.commit()

    return redirect(url_for("edit", id=post.id))

@app.route("/edit/<int:id>", methods=["GET","POST"])
@requires_authentication
def edit(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)

    if request.method == "GET":
        return render_template("edit.html", post=post)
    else:
        if post.title != request.form.get("post_title", ""):
            post.title = request.form.get("post_title","")
            post.slug = slugify(post.title)
        post.text = request.form.get("post_content","")
        post.updated_at = datetime.datetime.now()

        if any(request.form.getlist("post_draft", type=int)):
            post.draft = True
        else:
            post.draft = False

        db.session.add(post)
        db.session.commit()
        return redirect(url_for("edit", id=id))

@app.route("/delete/<int:id>", methods=["GET","POST"])
@requires_authentication
def delete(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        flash("Error deleting post ID %s"%id, category="error")
    else:
        db.session.delete(post)
        db.session.commit()

    return redirect(request.args.get("next","") or request.referrer or url_for('index'))

@app.route("/admin", methods=["GET", "POST"])
@requires_authentication
def admin():
    drafts = db.session.query(Post).filter_by(draft=True)\
                                          .order_by(Post.created_at.desc()).all()
    posts  = db.session.query(Post).filter_by(draft=False)\
                                          .order_by(Post.created_at.desc()).all()
    return render_template("admin.html", drafts=drafts, posts=posts)

@app.route("/admin/save/<int:id>", methods=["POST"])
@requires_authentication
def save_post(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)
    if post.title != request.form.get("title", ""):
        post.title = request.form.get("title","")
        post.slug = slugify(post.title)
    post.text = request.form.get("content", "")
    post.updated_at = datetime.datetime.now()
    db.session.add(post)
    db.session.commit()
    return jsonify(success=True)

@app.route("/preview/<int:id>")
@requires_authentication
def preview(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)

    return render_template("post_preview.html", post=post)

@app.route("/posts.rss")
def feed():
    posts = db.session.query(Post).filter_by(draft=False).order_by(Post.created_at.desc()).limit(10).all()

    r = make_response(render_template('index.xml', posts=posts))
    r.mimetype = "application/xml"
    return r

def slugify(text, delim=u'-'):
    """Generates an slightly worse ASCII-only slug."""
    result = []
    for word in _punct_re.split(text.lower()):
        word = normalize('NFKD', unicode(word)).encode('ascii', 'ignore')
        if word:
            result.append(word)
    slug = unicode(delim.join(result))
    # This could have issues if a post is marked as draft, then live, then draft, then live and there are > 1 posts
    # with the same slug. Oh well.
    _c = db.session.query(Post).filter_by(slug=slug).count()
    if _c > 0:
        return "%s%s%s"%(slug, delim, _c)
    else:
        return slug

if __name__ == "__main__":
    app.run()<commit_msg>Small performance fix<commit_after>from functools import wraps
from flask import render_template, request, Flask, flash, redirect, url_for, abort, jsonify, Response, make_response
import re
from unicodedata import normalize
from flask.ext.sqlalchemy import SQLAlchemy
import datetime
import markdown
from werkzeug.security import check_password_hash
import time

app = Flask(__name__)
app.config.from_object('settings')
db = SQLAlchemy(app)

_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')

markdown_parser = markdown.Markdown(extensions=['fenced_code'], output_format="html5", safe_mode=True)

class Post(db.Model):
    __tablename__ = "posts"
    id    = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String())
    slug  = db.Column(db.String(), unique=True)
    text  = db.Column(db.String(), default="")
    draft = db.Column(db.Boolean(), index=True, default=True)
    views = db.Column(db.Integer(), default=0)
    created_at = db.Column(db.DateTime, index=True)
    updated_at = db.Column(db.DateTime)

    def render_content(self):
        return markdown_parser.convert(self.text)

try:
    db.create_all()
except Exception:
    pass

def is_admin():
    auth = request.authorization
    if not auth or not (auth.username == app.config["ADMIN_USERNAME"]
                        and check_password_hash(app.config["ADMIN_PASSWORD"], auth.password)):
        return False
    return True

def requires_authentication(f):
    @wraps(f)
    def _auth_decorator(*args, **kwargs):
        if not is_admin():
            return Response("Could not authenticate you", 401, {"WWW-Authenticate":'Basic realm="Login Required"'})
        return f(*args, **kwargs)

    return _auth_decorator

@app.route("/")
def index():
    page = request.args.get("page", 0, type=int)
    posts_master = db.session.query(Post).filter_by(draft=False).order_by(Post.created_at.desc())
    posts_count = posts_master.count()

    posts = posts_master.limit(app.config["POSTS_PER_PAGE"]).offset(page*app.config["POSTS_PER_PAGE"]).all()
    is_more = posts_count > ((page*app.config["POSTS_PER_PAGE"]) + app.config["POSTS_PER_PAGE"])

    return render_template("index.html", posts=posts, now=datetime.datetime.now(),
                                         is_more=is_more, current_page=page, is_admin=is_admin())

@app.route("/<int:post_id>")
def view_post(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id, draft=False).one()
    except Exception:
        return abort(404)

    db.session.query(Post).filter_by(id=post_id).update({Post.views:Post.views+1})
    db.session.commit()

    return render_template("view.html", post=post, is_admin=is_admin())

@app.route("/<slug>")
def view_post_slug(slug):
    try:
        post = db.session.query(Post).filter_by(slug=slug,draft=False).one()
    except Exception:
        return abort(404)

    if not any(botname in request.user_agent.string for botname in ['Googlebot','Slurp','Twiceler','msnbot',
                                                                    'KaloogaBot','YodaoBot','"Baiduspider',
                                                                    'googlebot','Speedy Spider','DotBot']):
        db.session.query(Post).filter_by(slug=slug).update({Post.views:Post.views+1})
        db.session.commit()

    pid = request.args.get("pid", "0")
    return render_template("view.html", post=post, pid=pid, is_admin=is_admin())

@app.route("/new", methods=["POST", "GET"])
@requires_authentication
def new_post():
    post = Post()
    post.title = request.form.get("title","untitled")
    post.slug = slugify(post.title)
    post.created_at = datetime.datetime.now()
    post.updated_at = datetime.datetime.now()

    db.session.add(post)
    db.session.commit()

    return redirect(url_for("edit", id=post.id))

@app.route("/edit/<int:id>", methods=["GET","POST"])
@requires_authentication
def edit(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)

    if request.method == "GET":
        return render_template("edit.html", post=post)
    else:
        if post.title != request.form.get("post_title", ""):
            post.title = request.form.get("post_title","")
            post.slug = slugify(post.title)
        post.text = request.form.get("post_content","")
        post.updated_at = datetime.datetime.now()

        if any(request.form.getlist("post_draft", type=int)):
            post.draft = True
        else:
            post.draft = False

        db.session.add(post)
        db.session.commit()
        return redirect(url_for("edit", id=id))

@app.route("/delete/<int:id>", methods=["GET","POST"])
@requires_authentication
def delete(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        flash("Error deleting post ID %s"%id, category="error")
    else:
        db.session.delete(post)
        db.session.commit()

    return redirect(request.args.get("next","") or request.referrer or url_for('index'))

@app.route("/admin", methods=["GET", "POST"])
@requires_authentication
def admin():
    drafts = db.session.query(Post).filter_by(draft=True)\
                                          .order_by(Post.created_at.desc()).all()
    posts  = db.session.query(Post).filter_by(draft=False)\
                                          .order_by(Post.created_at.desc()).all()
    return render_template("admin.html", drafts=drafts, posts=posts)

@app.route("/admin/save/<int:id>", methods=["POST"])
@requires_authentication
def save_post(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)
    if post.title != request.form.get("title", ""):
        post.title = request.form.get("title","")
        post.slug = slugify(post.title)
    post.text = request.form.get("content", "")
    post.updated_at = datetime.datetime.now()
    db.session.add(post)
    db.session.commit()
    return jsonify(success=True)

@app.route("/preview/<int:id>")
@requires_authentication
def preview(id):
    try:
        post = db.session.query(Post).filter_by(id=id).one()
    except Exception:
        return abort(404)

    return render_template("post_preview.html", post=post)

@app.route("/posts.rss")
def feed():
    posts = db.session.query(Post).filter_by(draft=False).order_by(Post.created_at.desc()).limit(10).all()

    r = make_response(render_template('index.xml', posts=posts))
    r.mimetype = "application/xml"
    return r

def slugify(text, delim=u'-'):
    """Generates an slightly worse ASCII-only slug."""
    result = []
    for word in _punct_re.split(text.lower()):
        word = normalize('NFKD', unicode(word)).encode('ascii', 'ignore')
        if word:
            result.append(word)
    slug = unicode(delim.join(result))
    # This could have issues if a post is marked as draft, then live, then draft, then live and there are > 1 posts
    # with the same slug. Oh well.
    _c = db.session.query(Post).filter_by(slug=slug).count()
    if _c > 0:
        return "%s%s%s"%(slug, delim, _c)
    else:
        return slug

if __name__ == "__main__":
    app.run()<|endoftext|>


<commit_before>""" simple """

# python imports
import re
import datetime
import os
from functools import wraps
from unicodedata import normalize

# web stuff and markdown imports
import markdown
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.security import check_password_hash
from flask import render_template, request, Flask, flash, redirect, url_for, \
                  abort, jsonify, Response, make_response
from werkzeug.contrib.cache import FileSystemCache, NullCache

app = Flask(__name__)
app.config.from_object('settings')
db = SQLAlchemy(app)
cache_directory = os.path.dirname(__file__)
try:
    cache = FileSystemCache(os.path.join(cache_directory, "cache"))
except Exception,e:
    print "Could not create cache folder, caching will be disabled."
    print "Error: %s"%e
    cache = NullCache()

_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')

MARKDOWN_PARSER = markdown.Markdown(extensions=['fenced_code'],
                                    output_format="html5",
                                    safe_mode=True)

class Post(db.Model):
    def __init__(self, title=None, created_at=None):
        if title:
            self.title = title
            self.slug = slugify(title)
        if created_at:
            self.created_at = created_at
            self.updated_at = created_at

    __tablename__ = "posts"
    id    = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String())
    slug  = db.Column(db.LargeBinary(), unique=True)
    text  = db.Column(db.String(), default="")
    draft = db.Column(db.Boolean(), index=True, default=True)
    views = db.Column(db.Integer(), default=0)
    created_at = db.Column(db.DateTime, index=True)
    updated_at = db.Column(db.DateTime)

    def render_content(self):
        _cached = cache.get("post_%s"%self.id)
        if _cached is not None:
            return _cached
        text = MARKDOWN_PARSER.convert(self.text)
        cache.set("post_%s"%self.id, text)
        return text

    def set_content(self, content):
        cache.delete("post_%s"%self.id)
        self.text = content

try:
    db.create_all()
except Exception:
    pass

def is_admin():
    auth = request.authorization
    if not auth or not (auth.username == app.config["ADMIN_USERNAME"]
                        and check_password_hash(app.config["ADMIN_PASSWORD"],
                                                auth.password)):
        return False
    return True

def requires_authentication(func):
    """ function decorator for handling authentication """
    @wraps(func)
    def _auth_decorator(*args, **kwargs):
        """ does the wrapping """
        if not is_admin():
            return Response("Could not authenticate you",
                            401,
                            {"WWW-Authenticate":'Basic realm="Login Required"'})
        return func(*args, **kwargs)

    return _auth_decorator

@app.route("/")
def index():
    """ Index Page. Here is where the magic starts """
    page = request.args.get("page", 0, type=int)
    posts_master = db.session.query(Post)\
                       .filter_by(draft=False)\
                       .order_by(Post.created_at.desc())

    posts_count = posts_master.count()

    posts = posts_master\
                .limit(app.config["POSTS_PER_PAGE"])\
                .offset(page * app.config["POSTS_PER_PAGE"])\
                .all()

    # Sorry for the verbose names, but this seemed like a sensible
    # thing to do.
    last_possible_post_on_page = page * app.config["POSTS_PER_PAGE"]\
                               + app.config["POSTS_PER_PAGE"]
    there_is_more = posts_count > last_possible_post_on_page

    return render_template("index.html",
                           posts=posts,
                           now=datetime.datetime.now(),
                           is_more=there_is_more,
                           current_page=page,
                           is_admin=is_admin())

@app.route("/style.css")
def render_font_style():
    t = render_template("font_style.css",
                            font_name=app.config["FONT_NAME"])
    return Response(t, mimetype="text/css")

@app.route("/<int:post_id>")
def view_post(post_id):
    """ view_post renders a post and returns the Response object """
    try:
        post = db.session.query(Post).filter_by(id=post_id, draft=False).one()
    except Exception:
        return abort(404)

    db.session.query(Post)\
        .filter_by(id=post_id)\
        .update({Post.views:Post.views + 1})
    db.session.commit()

    return render_template("view.html", post=post, is_admin=is_admin())

@app.route("/<slug>")
def view_post_slug(slug):
    slug = slug.encode('utf-8')

    try:
        post = db.session.query(Post).filter_by(slug=slug, draft=False).one()
    except Exception:
        #TODO: Better exception
        return abort(404)

    if not any(botname in request.user_agent.string for botname in
                ['Googlebot',  'Slurp',         'Twiceler',     'msnbot',
                 'KaloogaBot', 'YodaoBot',      '"Baiduspider',
                 'googlebot',  'Speedy Spider', 'DotBot']):
        db.session.query(Post)\
            .filter_by(slug=slug)\
            .update({Post.views:Post.views+1})
        db.session.commit()

    pid = request.args.get("pid", "0")
    return render_template("view.html", post=post, pid=pid, is_admin=is_admin())

@app.route("/new", methods=["POST", "GET"])
@requires_authentication
def new_post():
    post = Post(title=request.form.get("title","untitled"),
                created_at=datetime.datetime.now())

    db.session.add(post)
    db.session.commit()

    return redirect(url_for("edit", post_id=post.id))

@app.route("/edit/<int:post_id>", methods=["GET","POST"])
@requires_authentication
def edit(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        #TODO: better exception
        return abort(404)

    if request.method == "GET":
        return render_template("edit.html", post=post)
    else:
        if post.title != request.form.get("post_title", ""):
            post.title = request.form.get("post_title","")
            post.slug = slugify(post.title)
        post.set_content(request.form.get("post_content",""))
        post.updated_at = datetime.datetime.now()

        if any(request.form.getlist("post_draft", type=int)):
            post.draft = True
        else:
            post.draft = False

        db.session.add(post)
        db.session.commit()
        return redirect(url_for("edit", post_id=post_id))

@app.route("/delete/<int:post_id>", methods=["GET","POST"])
@requires_authentication
def delete(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO: define better exceptions for db failure.
        flash("Error deleting post ID %s"%post_id, category="error")
    else:
        db.session.delete(post)
        db.session.commit()

    return redirect(request.args.get("next","")
        or request.referrer
        or url_for('index'))

@app.route("/admin", methods=["GET", "POST"])
@requires_authentication
def admin():
    drafts = db.session.query(Post)\
                 .filter_by(draft=True)\
                 .order_by(Post.created_at.desc()).all()
    posts  = db.session.query(Post)\
                 .filter_by(draft=False)\
                 .order_by(Post.created_at.desc()).all()
    return render_template("admin.html", drafts=drafts, posts=posts)

@app.route("/admin/save/<int:post_id>", methods=["POST"])
@requires_authentication
def save_post(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO Better exception
        return abort(404)
    if post.title != request.form.get("title", ""):
        post.title = request.form.get("title","")
        post.slug = slugify(post.title)
    post.set_content(request.form.get("content", ""))
    post.updated_at = datetime.datetime.now()
    db.session.add(post)
    db.session.commit()
    return jsonify(success=True)

@app.route("/preview/<int:post_id>")
@requires_authentication
def preview(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO: Better exception
        return abort(404)

    return render_template("post_preview.html", post=post)

@app.route("/posts.rss")
def feed():
    posts = db.session.query(Post)\
                .filter_by(draft=False)\
                .order_by(Post.created_at.desc())\
                .limit(10)\
                .all()

    response = make_response(render_template('index.xml', posts=posts))
    response.mimetype = "application/xml"
    return response

def slugify(text, delim='-'):
    """Generates an slightly worse ASCII-only slug."""
    result = []
    for word in _punct_re.split(text.lower()):
        word = normalize('NFKD', unicode(word)).encode('utf-8')
        if word:
            result.append(word)
    slug = delim.join(result)
    # This could have issues if a post is marked as draft, then live, then
    # draft, then live and there are > 1 posts with the same slug. Oh well.
    count = db.session.query(Post).filter_by(slug=slug).count()
    if count > 0:
        return "%s%s%s" % (slug, delim, count)
    else:
        return slug

if __name__ == "__main__":
    # Listen on all interfaces. This is so I could view the page on my iPhone/WP7 *not* so you can deploy using this file.

    app.run(host="0.0.0.0")
<commit_msg>Has to be .String(), .LargeBinary() can't be filtered on it seems<commit_after>""" simple """

# python imports
import re
import datetime
import os
from functools import wraps
from unicodedata import normalize

# web stuff and markdown imports
import markdown
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.security import check_password_hash
from flask import render_template, request, Flask, flash, redirect, url_for, \
                  abort, jsonify, Response, make_response
from werkzeug.contrib.cache import FileSystemCache, NullCache

app = Flask(__name__)
app.config.from_object('settings')
db = SQLAlchemy(app)
cache_directory = os.path.dirname(__file__)
try:
    cache = FileSystemCache(os.path.join(cache_directory, "cache"))
except Exception,e:
    print "Could not create cache folder, caching will be disabled."
    print "Error: %s"%e
    cache = NullCache()

_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')

MARKDOWN_PARSER = markdown.Markdown(extensions=['fenced_code'],
                                    output_format="html5",
                                    safe_mode=True)

class Post(db.Model):
    def __init__(self, title=None, created_at=None):
        if title:
            self.title = title
            self.slug = slugify(title)
        if created_at:
            self.created_at = created_at
            self.updated_at = created_at

    __tablename__ = "posts"
    id    = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String())
    slug  = db.Column(db.String(), unique=True)
    text  = db.Column(db.String(), default="")
    draft = db.Column(db.Boolean(), index=True, default=True)
    views = db.Column(db.Integer(), default=0)
    created_at = db.Column(db.DateTime, index=True)
    updated_at = db.Column(db.DateTime)

    def render_content(self):
        _cached = cache.get("post_%s"%self.id)
        if _cached is not None:
            return _cached
        text = MARKDOWN_PARSER.convert(self.text)
        cache.set("post_%s"%self.id, text)
        return text

    def set_content(self, content):
        cache.delete("post_%s"%self.id)
        self.text = content

try:
    db.create_all()
except Exception:
    pass

def is_admin():
    auth = request.authorization
    if not auth or not (auth.username == app.config["ADMIN_USERNAME"]
                        and check_password_hash(app.config["ADMIN_PASSWORD"],
                                                auth.password)):
        return False
    return True

def requires_authentication(func):
    """ function decorator for handling authentication """
    @wraps(func)
    def _auth_decorator(*args, **kwargs):
        """ does the wrapping """
        if not is_admin():
            return Response("Could not authenticate you",
                            401,
                            {"WWW-Authenticate":'Basic realm="Login Required"'})
        return func(*args, **kwargs)

    return _auth_decorator

@app.route("/")
def index():
    """ Index Page. Here is where the magic starts """
    page = request.args.get("page", 0, type=int)
    posts_master = db.session.query(Post)\
                       .filter_by(draft=False)\
                       .order_by(Post.created_at.desc())

    posts_count = posts_master.count()

    posts = posts_master\
                .limit(app.config["POSTS_PER_PAGE"])\
                .offset(page * app.config["POSTS_PER_PAGE"])\
                .all()

    # Sorry for the verbose names, but this seemed like a sensible
    # thing to do.
    last_possible_post_on_page = page * app.config["POSTS_PER_PAGE"]\
                               + app.config["POSTS_PER_PAGE"]
    there_is_more = posts_count > last_possible_post_on_page

    return render_template("index.html",
                           posts=posts,
                           now=datetime.datetime.now(),
                           is_more=there_is_more,
                           current_page=page,
                           is_admin=is_admin())

@app.route("/style.css")
def render_font_style():
    t = render_template("font_style.css",
                            font_name=app.config["FONT_NAME"])
    return Response(t, mimetype="text/css")

@app.route("/<int:post_id>")
def view_post(post_id):
    """ view_post renders a post and returns the Response object """
    try:
        post = db.session.query(Post).filter_by(id=post_id, draft=False).one()
    except Exception:
        return abort(404)

    db.session.query(Post)\
        .filter_by(id=post_id)\
        .update({Post.views:Post.views + 1})
    db.session.commit()

    return render_template("view.html", post=post, is_admin=is_admin())

@app.route("/<slug>")
def view_post_slug(slug):
    slug = slug.encode('utf-8')

    try:
        post = db.session.query(Post).filter_by(slug=slug, draft=False).one()
    except Exception:
        #TODO: Better exception
        return abort(404)

    if not any(botname in request.user_agent.string for botname in
                ['Googlebot',  'Slurp',         'Twiceler',     'msnbot',
                 'KaloogaBot', 'YodaoBot',      '"Baiduspider',
                 'googlebot',  'Speedy Spider', 'DotBot']):
        db.session.query(Post)\
            .filter_by(slug=slug)\
            .update({Post.views:Post.views+1})
        db.session.commit()

    pid = request.args.get("pid", "0")
    return render_template("view.html", post=post, pid=pid, is_admin=is_admin())

@app.route("/new", methods=["POST", "GET"])
@requires_authentication
def new_post():
    post = Post(title=request.form.get("title","untitled"),
                created_at=datetime.datetime.now())

    db.session.add(post)
    db.session.commit()

    return redirect(url_for("edit", post_id=post.id))

@app.route("/edit/<int:post_id>", methods=["GET","POST"])
@requires_authentication
def edit(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        #TODO: better exception
        return abort(404)

    if request.method == "GET":
        return render_template("edit.html", post=post)
    else:
        if post.title != request.form.get("post_title", ""):
            post.title = request.form.get("post_title","")
            post.slug = slugify(post.title)
        post.set_content(request.form.get("post_content",""))
        post.updated_at = datetime.datetime.now()

        if any(request.form.getlist("post_draft", type=int)):
            post.draft = True
        else:
            post.draft = False

        db.session.add(post)
        db.session.commit()
        return redirect(url_for("edit", post_id=post_id))

@app.route("/delete/<int:post_id>", methods=["GET","POST"])
@requires_authentication
def delete(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO: define better exceptions for db failure.
        flash("Error deleting post ID %s"%post_id, category="error")
    else:
        db.session.delete(post)
        db.session.commit()

    return redirect(request.args.get("next","")
        or request.referrer
        or url_for('index'))

@app.route("/admin", methods=["GET", "POST"])
@requires_authentication
def admin():
    drafts = db.session.query(Post)\
                 .filter_by(draft=True)\
                 .order_by(Post.created_at.desc()).all()
    posts  = db.session.query(Post)\
                 .filter_by(draft=False)\
                 .order_by(Post.created_at.desc()).all()
    return render_template("admin.html", drafts=drafts, posts=posts)

@app.route("/admin/save/<int:post_id>", methods=["POST"])
@requires_authentication
def save_post(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO Better exception
        return abort(404)
    if post.title != request.form.get("title", ""):
        post.title = request.form.get("title","")
        post.slug = slugify(post.title)
    post.set_content(request.form.get("content", ""))
    post.updated_at = datetime.datetime.now()
    db.session.add(post)
    db.session.commit()
    return jsonify(success=True)

@app.route("/preview/<int:post_id>")
@requires_authentication
def preview(post_id):
    try:
        post = db.session.query(Post).filter_by(id=post_id).one()
    except Exception:
        # TODO: Better exception
        return abort(404)

    return render_template("post_preview.html", post=post)

@app.route("/posts.rss")
def feed():
    posts = db.session.query(Post)\
                .filter_by(draft=False)\
                .order_by(Post.created_at.desc())\
                .limit(10)\
                .all()

    response = make_response(render_template('index.xml', posts=posts))
    response.mimetype = "application/xml"
    return response

def slugify(text, delim='-'):
    """Generates an slightly worse ASCII-only slug."""
    result = []
    for word in _punct_re.split(text.lower()):
        word = normalize('NFKD', unicode(word)).encode('utf-8')
        if word:
            result.append(word)
    slug = delim.join(result)
    # This could have issues if a post is marked as draft, then live, then
    # draft, then live and there are > 1 posts with the same slug. Oh well.
    count = db.session.query(Post).filter_by(slug=slug).count()
    if count > 0:
        return "%s%s%s" % (slug, delim, count)
    else:
        return slug

if __name__ == "__main__":
    # Listen on all interfaces. This is so I could view the page on my iPhone/WP7 *not* so you can deploy using this file.

    app.run(host="0.0.0.0")
<|endoftext|>


<commit_before>from django.db import models

class Page(models.Model):
    """A Page in Dyanote."""
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, default='')
    parent = models.ForeignKey('api.Page', null=True, related_name='children')
    body = models.TextField(blank=True, default='')
    author = models.ForeignKey('auth.User', related_name='pages')

    NORMAL = 0
    ROOT = 1
    TRASH = 2
    FLAGS = (
        (NORMAL, 'Normal page'),
        (ROOT, 'Root page'),
        (TRASH, 'Trash page'),
    )
    flags = models.IntegerField(choices=FLAGS, default=NORMAL)

    class Meta:
        ordering = ('created',)
<commit_msg>Mark Page's parent field as 'blank'<commit_after>from django.db import models

class Page(models.Model):
    """A Page in Dyanote."""
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, default='')
    parent = models.ForeignKey('api.Page', null=True, blank=True, related_name='children')
    body = models.TextField(blank=True, default='')
    author = models.ForeignKey('auth.User', related_name='pages')

    NORMAL = 0
    ROOT = 1
    TRASH = 2
    FLAGS = (
        (NORMAL, 'Normal page'),
        (ROOT, 'Root page'),
        (TRASH, 'Trash page'),
    )
    flags = models.IntegerField(choices=FLAGS, default=NORMAL)

    class Meta:
        ordering = ('created',)
<|endoftext|>