<commit_before>"""

  fs.tests.test_watch:  testcases for change watcher support

"""

import os
import sys
import time
import gc
import pickle
import unittest

from fs.path import *
from fs.errors import *
from fs.watch import *
from fs.tests import FSTestCases

try:
    from fs.osfs import watch_inotify
except ImportError:
    watch_inotify = None

if sys.platform == "win32":
    try:
        from fs.osfs import watch_win32
    except ImportError:
        watch_win32 = None
else:
    watch_win32 = None

class WatcherTestCases:
    """Testcases for filesystems providing change watcher support.

    This class should be used as a mixin to the unittest.TestCase class
    for filesystems that provide change watcher support.
    """

    def setupWatchers(self):
        self._captured_events = []
        self.watchfs.add_watcher(self._captured_events.append)

    def clearCapturedEvents(self):
        del self._captured_events[:]

    def waitForEvents(self):
        if isinstance(self.watchfs,PollingWatchableFS):
            self.watchfs._poll_cond.acquire()
            self.watchfs._poll_cond.wait()
            self.watchfs._poll_cond.wait()
            self.watchfs._poll_cond.release()
        else:
            time.sleep(2)#0.5)

    def assertEventOccurred(self,cls,path=None,**attrs):
        if not self.checkEventOccurred(cls,path,**attrs):
            args = (cls.__name__,path,attrs)
            assert False, "Event did not occur: %s(%s,%s)" % args

    def checkEventOccurred(self,cls,path=None,**attrs):
        self.waitForEvents()
        for event in self._captured_events:
            if isinstance(event,cls):
                if path is None or event.path == path:
                    for (k,v) in attrs.iteritems():
                        if getattr(event,k) != v:
                            break
                    else:
                        # all attrs match - found it!
                        return True
        return False

    def test_watch_makedir(self):
        self.setupWatchers()
        self.fs.makedir("test1")
        self.assertEventOccurred(CREATED,"/test1")

    def test_watch_makedir_with_two_watchers(self):
        self.setupWatchers()
        events2 = []
        self.watchfs.add_watcher(events2.append)
        self.fs.makedir("test1")
        self.assertEventOccurred(CREATED,"/test1")
        self.assertTrue(isinstance(events2[0],CREATED))

    def test_watch_readfile(self):
        self.setupWatchers()
        self.fs.setcontents("hello","hello world")
        self.assertEventOccurred(CREATED,"/hello")
        self.clearCapturedEvents()
        old_atime = self.fs.getinfo("hello").get("accessed_time")
        self.assertEquals(self.fs.getcontents("hello"),"hello world")
        if not isinstance(self.watchfs,PollingWatchableFS):
            #  Help it along by updting the atime.
            #  TODO: why is this necessary?
            if self.fs.hassyspath("hello"):
                syspath = self.fs.getsyspath("hello")
                mtime = os.stat(syspath).st_mtime
                atime = int(time.time())
                os.utime(self.fs.getsyspath("hello"),(atime,mtime))
            self.assertEventOccurred(ACCESSED,"/hello")
        elif old_atime is not None:
            #  Some filesystems don't update atime synchronously, or only
            #  update it if it's too old, or don't update it at all!
            #  Try to force the issue, wait for it to change, but eventually
            #  give up and bail out.
            for i in xrange(10):
                if self.fs.getinfo("hello").get("accessed_time") != old_atime:
                   if not self.checkEventOccurred(MODIFIED,"/hello"):
                       self.assertEventOccurred(ACCESSED,"/hello")
                   break
                time.sleep(0.2)
                if self.fs.hassyspath("hello"):
                    syspath = self.fs.getsyspath("hello")
                    mtime = os.stat(syspath).st_mtime
                    atime = int(time.time())
                    os.utime(self.fs.getsyspath("hello"),(atime,mtime))

    def test_watch_writefile(self):
        self.setupWatchers()
        self.fs.setcontents("hello","hello world")
        self.assertEventOccurred(CREATED,"/hello")
        self.clearCapturedEvents()
        self.fs.setcontents("hello","hello again world")
        self.assertEventOccurred(MODIFIED,"/hello")

    def test_watch_single_file(self):
        self.fs.setcontents("hello","hello world")
        events = []
        self.watchfs.add_watcher(events.append,"/hello",(MODIFIED,))
        self.fs.setcontents("hello","hello again world")
        self.fs.remove("hello")
        self.waitForEvents()
        for evt in events:
            assert isinstance(evt,MODIFIED)
            self.assertEquals(evt.path,"/hello")

    def test_watch_single_file_remove(self):
        self.fs.makedir("testing")
        self.fs.setcontents("testing/hello","hello world")
        events = []
        self.watchfs.add_watcher(events.append,"/testing/hello",(REMOVED,))
        self.fs.setcontents("testing/hello","hello again world")
        self.waitForEvents()
        self.fs.remove("testing/hello")
        self.waitForEvents()
        self.assertEquals(len(events),1)
        assert isinstance(events[0],REMOVED)
        self.assertEquals(events[0].path,"/testing/hello")

    def test_watch_iter_changes(self):
        changes = iter_changes(self.watchfs)
        self.fs.makedir("test1")
        self.fs.setcontents("test1/hello","hello world")
        self.waitForEvents()
        self.fs.removedir("test1",force=True)
        self.waitForEvents()
        self.watchfs.close()
        #  Locate the CREATED(test1) event
        event = changes.next(timeout=1)
        while not isinstance(event,CREATED) or event.path != "/test1":
            event = changes.next(timeout=1)
        #  Locate the CREATED(test1/hello) event
        event = changes.next(timeout=1)
        while not isinstance(event,CREATED) or event.path != "/test1/hello":
            event = changes.next(timeout=1)
        #  Locate the REMOVED(test1) event
        event = changes.next(timeout=1)
        while not isinstance(event,REMOVED) or event.path != "/test1":
            event = changes.next(timeout=1)
        #  Locate the CLOSED event
        event = changes.next(timeout=1)
        while not isinstance(event,CLOSED):
            event = changes.next(timeout=1)
        #  That should be the last event in the list
        self.assertRaises(StopIteration,changes.next,timeout=1)
        changes.close()

from fs import tempfs, osfs
class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases):

    def setUp(self):
        self.fs = tempfs.TempFS()
        watchfs = osfs.OSFS(self.fs.root_path)
        self.watchfs = ensure_watchable(watchfs,poll_interval=0.1)
        if watch_inotify is not None:
            self.assertEquals(watchfs,self.watchfs)
        if watch_win32 is not None:
            self.assertEquals(watchfs,self.watchfs)

    def tearDown(self):
        self.watchfs.close()
        self.fs.close()

    def check(self, p):
        return self.fs.exists(p)

from fs import memoryfs
class TestWatchers_MemoryFS(unittest.TestCase,FSTestCases,WatcherTestCases):

    def setUp(self):
        self.fs = self.watchfs = WatchableFS(memoryfs.MemoryFS())

    def tearDown(self):
        self.watchfs.close()
        self.fs.close()

    def check(self, p):
        return self.fs.exists(p)

class TestWatchers_MemoryFS_polling(TestWatchers_MemoryFS):

    def setUp(self):
        self.fs = memoryfs.MemoryFS()
        self.watchfs = ensure_watchable(self.fs,poll_interval=0.1)

<commit_msg>Unit tests fixup - CREATED is somethimes the second event rather than the first<commit_after>"""

  fs.tests.test_watch:  testcases for change watcher support

"""

import os
import sys
import time
import gc
import pickle
import unittest

from fs.path import *
from fs.errors import *
from fs.watch import *
from fs.tests import FSTestCases

try:
    from fs.osfs import watch_inotify
except ImportError:
    watch_inotify = None

if sys.platform == "win32":
    try:
        from fs.osfs import watch_win32
    except ImportError:
        watch_win32 = None
else:
    watch_win32 = None

class WatcherTestCases:
    """Testcases for filesystems providing change watcher support.

    This class should be used as a mixin to the unittest.TestCase class
    for filesystems that provide change watcher support.
    """

    def setupWatchers(self):
        self._captured_events = []
        self.watchfs.add_watcher(self._captured_events.append)

    def clearCapturedEvents(self):
        del self._captured_events[:]

    def waitForEvents(self):
        if isinstance(self.watchfs,PollingWatchableFS):
            self.watchfs._poll_cond.acquire()
            self.watchfs._poll_cond.wait()
            self.watchfs._poll_cond.wait()
            self.watchfs._poll_cond.release()
        else:
            time.sleep(2)#0.5)

    def assertEventOccurred(self,cls,path=None,event_list=None,**attrs):
        if not self.checkEventOccurred(cls,path,event_list,**attrs):
            args = (cls.__name__,path,attrs)
            assert False, "Event did not occur: %s(%s,%s)" % args

    def checkEventOccurred(self,cls,path=None,event_list=None,**attrs):
        if event_list is None:
            event_list = self._captured_events
        self.waitForEvents()
        for event in event_list:
            if isinstance(event,cls):
                if path is None or event.path == path:
                    for (k,v) in attrs.iteritems():
                        if getattr(event,k) != v:
                            break
                    else:
                        # all attrs match - found it!
                        return True
        return False

    def test_watch_makedir(self):
        self.setupWatchers()
        self.fs.makedir("test1")
        self.assertEventOccurred(CREATED,"/test1")

    def test_watch_makedir_with_two_watchers(self):
        self.setupWatchers()
        events2 = []
        self.watchfs.add_watcher(events2.append)
        self.fs.makedir("test1")
        self.assertEventOccurred(CREATED,"/test1")
        self.assertEventOccurred(CREATED,"/test1",event_list=events2)

    def test_watch_readfile(self):
        self.setupWatchers()
        self.fs.setcontents("hello","hello world")
        self.assertEventOccurred(CREATED,"/hello")
        self.clearCapturedEvents()
        old_atime = self.fs.getinfo("hello").get("accessed_time")
        self.assertEquals(self.fs.getcontents("hello"),"hello world")
        if not isinstance(self.watchfs,PollingWatchableFS):
            #  Help it along by updting the atime.
            #  TODO: why is this necessary?
            if self.fs.hassyspath("hello"):
                syspath = self.fs.getsyspath("hello")
                mtime = os.stat(syspath).st_mtime
                atime = int(time.time())
                os.utime(self.fs.getsyspath("hello"),(atime,mtime))
            self.assertEventOccurred(ACCESSED,"/hello")
        elif old_atime is not None:
            #  Some filesystems don't update atime synchronously, or only
            #  update it if it's too old, or don't update it at all!
            #  Try to force the issue, wait for it to change, but eventually
            #  give up and bail out.
            for i in xrange(10):
                if self.fs.getinfo("hello").get("accessed_time") != old_atime:
                   if not self.checkEventOccurred(MODIFIED,"/hello"):
                       self.assertEventOccurred(ACCESSED,"/hello")
                   break
                time.sleep(0.2)
                if self.fs.hassyspath("hello"):
                    syspath = self.fs.getsyspath("hello")
                    mtime = os.stat(syspath).st_mtime
                    atime = int(time.time())
                    os.utime(self.fs.getsyspath("hello"),(atime,mtime))

    def test_watch_writefile(self):
        self.setupWatchers()
        self.fs.setcontents("hello","hello world")
        self.assertEventOccurred(CREATED,"/hello")
        self.clearCapturedEvents()
        self.fs.setcontents("hello","hello again world")
        self.assertEventOccurred(MODIFIED,"/hello")

    def test_watch_single_file(self):
        self.fs.setcontents("hello","hello world")
        events = []
        self.watchfs.add_watcher(events.append,"/hello",(MODIFIED,))
        self.fs.setcontents("hello","hello again world")
        self.fs.remove("hello")
        self.waitForEvents()
        for evt in events:
            assert isinstance(evt,MODIFIED)
            self.assertEquals(evt.path,"/hello")

    def test_watch_single_file_remove(self):
        self.fs.makedir("testing")
        self.fs.setcontents("testing/hello","hello world")
        events = []
        self.watchfs.add_watcher(events.append,"/testing/hello",(REMOVED,))
        self.fs.setcontents("testing/hello","hello again world")
        self.waitForEvents()
        self.fs.remove("testing/hello")
        self.waitForEvents()
        self.assertEquals(len(events),1)
        assert isinstance(events[0],REMOVED)
        self.assertEquals(events[0].path,"/testing/hello")

    def test_watch_iter_changes(self):
        changes = iter_changes(self.watchfs)
        self.fs.makedir("test1")
        self.fs.setcontents("test1/hello","hello world")
        self.waitForEvents()
        self.fs.removedir("test1",force=True)
        self.waitForEvents()
        self.watchfs.close()
        #  Locate the CREATED(test1) event
        event = changes.next(timeout=1)
        while not isinstance(event,CREATED) or event.path != "/test1":
            event = changes.next(timeout=1)
        #  Locate the CREATED(test1/hello) event
        event = changes.next(timeout=1)
        while not isinstance(event,CREATED) or event.path != "/test1/hello":
            event = changes.next(timeout=1)
        #  Locate the REMOVED(test1) event
        event = changes.next(timeout=1)
        while not isinstance(event,REMOVED) or event.path != "/test1":
            event = changes.next(timeout=1)
        #  Locate the CLOSED event
        event = changes.next(timeout=1)
        while not isinstance(event,CLOSED):
            event = changes.next(timeout=1)
        #  That should be the last event in the list
        self.assertRaises(StopIteration,changes.next,timeout=1)
        changes.close()

from fs import tempfs, osfs
class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases):

    def setUp(self):
        self.fs = tempfs.TempFS()
        watchfs = osfs.OSFS(self.fs.root_path)
        self.watchfs = ensure_watchable(watchfs,poll_interval=0.1)
        if watch_inotify is not None:
            self.assertEquals(watchfs,self.watchfs)
        if watch_win32 is not None:
            self.assertEquals(watchfs,self.watchfs)

    def tearDown(self):
        self.watchfs.close()
        self.fs.close()

    def check(self, p):
        return self.fs.exists(p)

from fs import memoryfs
class TestWatchers_MemoryFS(unittest.TestCase,FSTestCases,WatcherTestCases):

    def setUp(self):
        self.fs = self.watchfs = WatchableFS(memoryfs.MemoryFS())

    def tearDown(self):
        self.watchfs.close()
        self.fs.close()

    def check(self, p):
        return self.fs.exists(p)

class TestWatchers_MemoryFS_polling(TestWatchers_MemoryFS):

    def setUp(self):
        self.fs = memoryfs.MemoryFS()
        self.watchfs = ensure_watchable(self.fs,poll_interval=0.1)

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2009-today OpenERP SA (<http://www.openerp.com>)
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>
#
##############################################################################

from openerp import SUPERUSER_ID
from osv import osv
from osv import fields
import tools

class mail_followers(osv.Model):
    """ mail_followers holds the data related to the follow mechanism inside
        OpenERP. Partners can choose to follow documents (records) of any kind
        that inherits from mail.thread. Following documents allow to receive
        notifications for new messages.
        A subscription is characterized by:
            :param: res_model: model of the followed objects
            :param: res_id: ID of resource (may be 0 for every objects)
    """
    _name = 'mail.followers'
    _rec_name = 'partner_id'
    _log_access = False
    _description = 'Document Followers'
    _columns = {
        'res_model': fields.char('Related Document Model', size=128,
                        required=True, select=1,
                        help='Model of the followed resource'),
        'res_id': fields.integer('Related Document ID', select=1,
                        help='Id of the followed resource'),
        'partner_id': fields.many2one('res.partner', string='Related Partner',
                        ondelete='cascade', required=True, select=1),
        'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
            help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
    }

class mail_notification(osv.Model):
    """ Class holding notifications pushed to partners. Followers and partners
        added in 'contacts to notify' receive notifications. """
    _name = 'mail.notification'
    _rec_name = 'partner_id'
    _log_access = False
    _description = 'Notifications'

    _columns = {
        'partner_id': fields.many2one('res.partner', string='Contact',
                        ondelete='cascade', required=True),
        'read': fields.boolean('Read'),
        'message_id': fields.many2one('mail.message', string='Message',
                        ondelete='cascade', required=True),
    }

    _defaults = {
        'read': False,
    }

    def init(self, cr):
        cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
        if not cr.fetchone():
            cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')

    def create(self, cr, uid, vals, context=None):
        """ Override of create to check that we can not create a notification
            for a message the user can not read. """
        if self.pool.get('mail.message').check_access_rights(cr, uid, 'read'):
            return super(mail_notification, self).create(cr, uid, vals, context=context)
        return False

    def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
        """ TDE note: add a comment, verify method calls, because js seems obfuscated. """
        # TDE note: child_of to set unread ?
        user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
        notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)

        # all message have notifications: already set them as (un)read
        if len(notif_ids) == len(msg_ids):
            return self.write(cr, uid, notif_ids, {'read': read}, context=context)

        # some messages do not have notifications: find which one, create notification, update read status
        exist_notification = dict.fromkeys(msg_ids, False)
        for notification in self.browse(cr, uid, notif_ids, context=context):
            exist_notification[notification.message_id] = True
        for msg_id in exist_notification.keys():
            self.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
        return self.write(cr, uid, notif_ids, {'read': read}, context=context)

    def get_partners_to_notify(self, cr, uid, message, context=None):
        """ Return the list of partners to notify, based on their preferences.

            :param browse_record message: mail.message to notify
        """
        notify_pids = []
        for notification in message.notification_ids:
            if notification.read:
                continue
            partner = notification.partner_id
            # Do not send an email to the writer
            if partner.user_ids and partner.user_ids[0].id == uid:
                continue
            # Do not send to partners without email address defined
            if not partner.email:
                continue
            # Partner does not want to receive any emails
            if partner.notification_email_send == 'none':
                continue
            # Partner wants to receive only emails and comments
            if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
                continue
            # Partner wants to receive only emails
            if partner.notification_email_send == 'email' and message.type != 'email':
                continue
            notify_pids.append(partner.id)
        return notify_pids

    def _notify(self, cr, uid, msg_id, context=None):
        """ Send by email the notification depending on the user preferences """
        if context is None:
            context = {}
        # mail_noemail (do not send email) or no partner_ids: do not send, return
        if context.get('mail_noemail'):
            return True
        msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)

        notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
        if not notify_partner_ids:
            return True

        mail_mail = self.pool.get('mail.mail')

        # TMP about thread formation
        if msg.parent_id.id:
            # 1. fetch all messages that will form the thread as SUPERUSER, filtering will be done after
            messages = self.pool.get('mail.message').message_read(cr, SUPERUSER_ID, None, domain=['|', ('id', 'in', [msg.id, msg.parent_id.id]), ('parent_id', '=', msg.parent_id.id)], context=context, limit=5, thread_level=1)
            for message in messages:
                print message['body'], message['id'], message['parent_id']

        # END TMP

        # add signature
        body_html = msg.body
        signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
        if signature:
            body_html = tools.append_content_to_html(body_html, signature)

        mail_values = {
            'mail_message_id': msg.id,
            'email_to': [],
            'auto_delete': True,
            'body_html': body_html,
            'state': 'outgoing',
        }
        mail_values['email_to'] = ', '.join(mail_values['email_to'])
        email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
        return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
<commit_msg>[REM] mail_followers: removed WIP about email quote.<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2009-today OpenERP SA (<http://www.openerp.com>)
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>
#
##############################################################################

from openerp import SUPERUSER_ID
from osv import osv
from osv import fields
import tools

class mail_followers(osv.Model):
    """ mail_followers holds the data related to the follow mechanism inside
        OpenERP. Partners can choose to follow documents (records) of any kind
        that inherits from mail.thread. Following documents allow to receive
        notifications for new messages.
        A subscription is characterized by:
            :param: res_model: model of the followed objects
            :param: res_id: ID of resource (may be 0 for every objects)
    """
    _name = 'mail.followers'
    _rec_name = 'partner_id'
    _log_access = False
    _description = 'Document Followers'
    _columns = {
        'res_model': fields.char('Related Document Model', size=128,
                        required=True, select=1,
                        help='Model of the followed resource'),
        'res_id': fields.integer('Related Document ID', select=1,
                        help='Id of the followed resource'),
        'partner_id': fields.many2one('res.partner', string='Related Partner',
                        ondelete='cascade', required=True, select=1),
        'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
            help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
    }

class mail_notification(osv.Model):
    """ Class holding notifications pushed to partners. Followers and partners
        added in 'contacts to notify' receive notifications. """
    _name = 'mail.notification'
    _rec_name = 'partner_id'
    _log_access = False
    _description = 'Notifications'

    _columns = {
        'partner_id': fields.many2one('res.partner', string='Contact',
                        ondelete='cascade', required=True),
        'read': fields.boolean('Read'),
        'message_id': fields.many2one('mail.message', string='Message',
                        ondelete='cascade', required=True),
    }

    _defaults = {
        'read': False,
    }

    def init(self, cr):
        cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
        if not cr.fetchone():
            cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')

    def create(self, cr, uid, vals, context=None):
        """ Override of create to check that we can not create a notification
            for a message the user can not read. """
        if self.pool.get('mail.message').check_access_rights(cr, uid, 'read'):
            return super(mail_notification, self).create(cr, uid, vals, context=context)
        return False

    def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
        """ TDE note: add a comment, verify method calls, because js seems obfuscated. """
        # TDE note: child_of to set unread ?
        user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
        notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)

        # all message have notifications: already set them as (un)read
        if len(notif_ids) == len(msg_ids):
            return self.write(cr, uid, notif_ids, {'read': read}, context=context)

        # some messages do not have notifications: find which one, create notification, update read status
        exist_notification = dict.fromkeys(msg_ids, False)
        for notification in self.browse(cr, uid, notif_ids, context=context):
            exist_notification[notification.message_id] = True
        for msg_id in exist_notification.keys():
            self.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
        return self.write(cr, uid, notif_ids, {'read': read}, context=context)

    def get_partners_to_notify(self, cr, uid, message, context=None):
        """ Return the list of partners to notify, based on their preferences.

            :param browse_record message: mail.message to notify
        """
        notify_pids = []
        for notification in message.notification_ids:
            if notification.read:
                continue
            partner = notification.partner_id
            # Do not send an email to the writer
            if partner.user_ids and partner.user_ids[0].id == uid:
                continue
            # Do not send to partners without email address defined
            if not partner.email:
                continue
            # Partner does not want to receive any emails
            if partner.notification_email_send == 'none':
                continue
            # Partner wants to receive only emails and comments
            if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
                continue
            # Partner wants to receive only emails
            if partner.notification_email_send == 'email' and message.type != 'email':
                continue
            notify_pids.append(partner.id)
        return notify_pids

    def _notify(self, cr, uid, msg_id, context=None):
        """ Send by email the notification depending on the user preferences """
        if context is None:
            context = {}
        # mail_noemail (do not send email) or no partner_ids: do not send, return
        if context.get('mail_noemail'):
            return True
        msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)

        notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
        if not notify_partner_ids:
            return True

        # add signature
        mail_mail = self.pool.get('mail.mail')
        body_html = msg.body
        signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
        if signature:
            body_html = tools.append_content_to_html(body_html, signature)

        mail_values = {
            'mail_message_id': msg.id,
            'email_to': [],
            'auto_delete': True,
            'body_html': body_html,
            'state': 'outgoing',
        }
        mail_values['email_to'] = ', '.join(mail_values['email_to'])
        email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
        return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
<|endoftext|>


<commit_before>import re
from collections import OrderedDict

class XigtMixin(object):
    """
    Common methods for accessing subelements in XigtCorpus, Igt, and
    Tier objects.
    """
    def __init__(self):
        self._list = []
        self._dict = OrderedDict()

    def __iter__(self):
        return iter(self._list)

    def __getitem__(self, obj_id):
        try:
            # attempt list indexing
            obj_id = int(obj_id)
            return self._list[obj_id]
        except ValueError:
            return self._dict[obj_id]

    def get(self, obj_id, default=None):
        try:
            return self[obj_id]
        except (KeyError, IndexError):
            return default

    def add(self, obj):
        obj._parent = self
        if obj.id is not None:
            if obj.id in self._dict:
                raise ValueError('Id "{}" already exists in collection.'
                                 .format(obj.id))
            self._dict[obj.id] = obj
        self._list.append(obj)

    def add_list(self, objs):
        if objs is not None:
            for obj in objs:
                self.add(obj)

class XigtCorpus(XigtMixin):
    """
    A container of Igt objects, as well as corpus-level attributes and
    metadata. In serialization formats (e.g. XigtXML), XigtCorpus
    becomes the root element.
    """
    def __init__(self, id=None, attributes=None, metadata=None, igts=None):
        XigtMixin.__init__(self)
        self.id = id
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(igts)

    @property
    def igts(self):
        return self._list

    @igts.setter
    def igts(self, value):
        self._list = value

class Igt(XigtMixin):
    """
    An IGT (Interlinear Glossed Text) instance.
    """
    def __init__(self, id=None, type=None, attributes=None, metadata=None,
                 tiers=None, corpus=None):
        XigtMixin.__init__(self)
        self.id = id
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(tiers)
        self._parent = corpus

    @property
    def corpus(self):
        return self._parent

    @property
    def tiers(self):
        return self._list

    @tiers.setter
    def tiers(self, value):
        self._list = value

class Tier(XigtMixin):
    """
    A tier of IGT data. A tier should contain homogenous Items of
    data, such as all words or all glosses.
    """
    def __init__(self, id=None, ref=None, type=None, attributes=None,
                 metadata=None, items=None, igt=None):
        XigtMixin.__init__(self)
        self.id = id
        self.ref = ref
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(items)
        self._parent = igt

    def __repr__(self):
        return 'Tier({},{},{},[{}])'.format(
            str(self.type), str(self.id), str(self.ref), ','.join(self.items))

    @property
    def igt(self):
        return self._parent

    @property
    def items(self):
        return self._list

    @items.setter
    def items(self, value):
        self._list = value

    @property
    def reftier(self):
        if self.ref is not None and self.igt is not None:
            return self.igt.get(self.ref)
        else:
            #TODO log this
            return None

class Item(object):
    """
    A single datum on a Tier. Often these are tokens, such as words
    or glosses, but may be phrases, translations, or (via extensions)
    more complex data like syntax nodes or dependencies.
    """
    def __init__(self, id=None, ref=None, type=None, attributes=None,
                 content=None, tier=None):
        self.id = id
        self.ref = ref
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.content = content
        self._parent = tier # mainly used for alignment expressions

    def __repr__(self):
        return 'Item({},{},{},{})'.format(
            map(str, [self.type, self.id, self.ref, self.content]))

    def __str__(self):
        return str(self.content)

    @property
    def tier(self):
        return self._parent

    def resolve_ref(self):
        if self.ref is not None and self.tier is not None:
            return resolve_alignment_expression(self.ref, self.tier.reftier)
        else:
            return None #TODO log this

    def span(self, start, end):
        if self.content is None:
            return None
        return self.content[start:end]

class Metadata(object):
    """
    A container for metadata on XigtCorpus, Igt, or Tier objects.
    Extensions may place constraints on the allowable metadata.
    """
    def __init__(self, type=None, attributes=None, content=None):
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.content = content

    def __repr__(self):
        return 'Metadata({},"{}")'.format(str(self.type), self.content)

### Alignment Expressions ####################################################

# Module variables
algnexpr_re = re.compile(r'(([a-zA-Z][\-.\w]*)(\[[^\]]*\])?|\+|,)')
selection_re = re.compile(r'(-?\d+:-?\d+|\+|,)')

delim1 = ''
delim2 = ' '

def resolve_alignment_expression(expression, tier, plus=delim1, comma=delim2):
    alignments = algnexpr_re.findall(expression)
    parts = [plus if match == '+' else
             comma if match == ',' else
             resolve_alignment(tier, item_id, selection)
             for match, item_id, selection in alignments]
    return ''.join(parts)

def resolve_alignment(tier, item_id, selection, plus=delim1, comma=delim2):
    item = tier.get(item_id)
    if selection == '':
        return item.content
    spans = selection_re.findall(selection)
    parts = [plus if match == '+' else
             comma if match == ',' else
             item.span(*map(int, match.split(':')))
             for match in spans]
    return ''.join(parts)
<commit_msg>Partial implementation of attribute inheritance.<commit_after>import re
from collections import OrderedDict

class XigtMixin(object):
    """
    Common methods for accessing subelements in XigtCorpus, Igt, and
    Tier objects.
    """
    def __init__(self):
        self._list = []
        self._dict = OrderedDict()

    def __iter__(self):
        return iter(self._list)

    def __getitem__(self, obj_id):
        try:
            # attempt list indexing
            obj_id = int(obj_id)
            return self._list[obj_id]
        except ValueError:
            return self._dict[obj_id]

    def get(self, obj_id, default=None):
        try:
            return self[obj_id]
        except (KeyError, IndexError):
            return default

    def add(self, obj):
        obj._parent = self
        if obj.id is not None:
            if obj.id in self._dict:
                raise ValueError('Id "{}" already exists in collection.'
                                 .format(obj.id))
            self._dict[obj.id] = obj
        self._list.append(obj)

    def add_list(self, objs):
        if objs is not None:
            for obj in objs:
                self.add(obj)

class XigtInheritanceMixin(object):
    """
    Enables the inheritance of attributes and metadata.
    """
    def __init__(self):
        self._local_attrs = set()

    def get_attribute(self, key, inherit=True):
        if key in self.attributes:
            return self.attributes[key]
        elif inherit and hasattr(self, _parent) and self._parent is not None:
            return self._parent.get_attribute(key, inherit=inherit)
        else:
            return None

    def get_meta(self, key, inherit=True):
        pass # what to do here?

class XigtCorpus(XigtMixin):
    """
    A container of Igt objects, as well as corpus-level attributes and
    metadata. In serialization formats (e.g. XigtXML), XigtCorpus
    becomes the root element.
    """
    def __init__(self, id=None, attributes=None, metadata=None, igts=None):
        XigtMixin.__init__(self)
        self.id = id
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(igts)

    @property
    def igts(self):
        return self._list

    @igts.setter
    def igts(self, value):
        self._list = value

class Igt(XigtMixin):
    """
    An IGT (Interlinear Glossed Text) instance.
    """
    def __init__(self, id=None, type=None, attributes=None, metadata=None,
                 tiers=None, corpus=None):
        XigtMixin.__init__(self)
        self.id = id
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(tiers)
        self._parent = corpus

    @property
    def corpus(self):
        return self._parent

    @property
    def tiers(self):
        return self._list

    @tiers.setter
    def tiers(self, value):
        self._list = value

class Tier(XigtMixin):
    """
    A tier of IGT data. A tier should contain homogenous Items of
    data, such as all words or all glosses.
    """
    def __init__(self, id=None, ref=None, type=None, attributes=None,
                 metadata=None, items=None, igt=None):
        XigtMixin.__init__(self)
        self.id = id
        self.ref = ref
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.metadata = metadata
        self.add_list(items)
        self._parent = igt

    def __repr__(self):
        return 'Tier({},{},{},[{}])'.format(
            str(self.type), str(self.id), str(self.ref), ','.join(self.items))

    @property
    def igt(self):
        return self._parent

    @property
    def items(self):
        return self._list

    @items.setter
    def items(self, value):
        self._list = value

    @property
    def reftier(self):
        if self.ref is not None and self.igt is not None:
            return self.igt.get(self.ref)
        else:
            #TODO log this
            return None

class Item(object):
    """
    A single datum on a Tier. Often these are tokens, such as words
    or glosses, but may be phrases, translations, or (via extensions)
    more complex data like syntax nodes or dependencies.
    """
    def __init__(self, id=None, ref=None, type=None, attributes=None,
                 content=None, tier=None):
        self.id = id
        self.ref = ref
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.content = content
        self._parent = tier # mainly used for alignment expressions

    def __repr__(self):
        return 'Item({},{},{},{})'.format(
            map(str, [self.type, self.id, self.ref, self.content]))

    def __str__(self):
        return str(self.content)

    @property
    def tier(self):
        return self._parent

    def resolve_ref(self):
        if self.ref is not None and self.tier is not None:
            return resolve_alignment_expression(self.ref, self.tier.reftier)
        else:
            return None #TODO log this

    def span(self, start, end):
        if self.content is None:
            return None
        return self.content[start:end]

class Metadata(object):
    """
    A container for metadata on XigtCorpus, Igt, or Tier objects.
    Extensions may place constraints on the allowable metadata.
    """
    def __init__(self, type=None, attributes=None, content=None):
        self.type = type
        self.attributes = attributes or OrderedDict()
        self.content = content

    def __repr__(self):
        return 'Metadata({},"{}")'.format(str(self.type), self.content)

### Alignment Expressions ####################################################

# Module variables
algnexpr_re = re.compile(r'(([a-zA-Z][\-.\w]*)(\[[^\]]*\])?|\+|,)')
selection_re = re.compile(r'(-?\d+:-?\d+|\+|,)')

delim1 = ''
delim2 = ' '

def resolve_alignment_expression(expression, tier, plus=delim1, comma=delim2):
    alignments = algnexpr_re.findall(expression)
    parts = [plus if match == '+' else
             comma if match == ',' else
             resolve_alignment(tier, item_id, selection)
             for match, item_id, selection in alignments]
    return ''.join(parts)

def resolve_alignment(tier, item_id, selection, plus=delim1, comma=delim2):
    item = tier.get(item_id)
    if selection == '':
        return item.content
    spans = selection_re.findall(selection)
    parts = [plus if match == '+' else
             comma if match == ',' else
             item.span(*map(int, match.split(':')))
             for match in spans]
    return ''.join(parts)
<|endoftext|>


<commit_before>#!/usr/bin/python -O

import random
import math
import argparse
import os
import sys
from pkg_resources import resource_filename

# The dictionary bundled with the script. It must be in the same
# dir as the script.
DEFAULT_DICT =  resource_filename(__name__, 'dict.txt')
# Uncomment to use the system dictionary as the default.
#DEFAULT_DICT="/usr/share/dict/words"
WORDS = 4
BAD_CH_LIST = ['\'']
VERSION_NUMBER="0.1.1"

class WordValidator(object):
    def __init__(self, flags):
        self._badChars = BAD_CH_LIST
        self._maxLen = flags.l
        self._skipBad = flags.x

    def isValidWord(self, word):
        if len(word) > self._maxLen or len(word) == 0:
            return False
        if self._skipBad and self._hasBadChar(word):
            return False
        return True

    def _hasBadChar(self, word):
        for char in word:
            if char in self._badChars:
                return True

class Dictionary(object):
    def __init__(self, flags):
        self._dictLen = 0
        self._wordValidator = WordValidator(flags)
        self.dict_path = flags.d

    def _openDict(self):
        try:
            dict_file = open(self.dict_path)
        except IOError as e:
            error_msg =  "There was a problem opening the dictionary file '%s': " % self.dict_path
            error_msg += str(e.args[1]) + "\n"
            sys.stderr.write(error_msg)
            exit(1)
        return dict_file

class DictionaryList(Dictionary):
    def __init__(self, flags):
        super(DictionaryList, self).__init__(flags)
        self._loadDict(flags.d)

    def __getitem__(self, key):
        return self._dictList[key]

    def __len__(self):
        return self._dictLen

    def _loadDict(self, path):
        self._dictList = []
        dict_file = self._openDict()
        for line in dict_file:
            word = line.strip()
            if self._wordValidator.isValidWord(word):
                self._dictList.append(word)
        self._dictLen = len(self._dictList)
        dict_file.close()

class RandomDict(object):
    def __init__(self, flags):
        self._dictObj = DictionaryList(flags)
        self._rng = random.SystemRandom()

    def __iter__(self):
        return self

    def __len__(self):
        return len(self._dictObj)

    def next(self):
        return self._dictObj[self._rng.randint(0, len(self) - 1)]

    def __getattr__(self, attr):
        return getattr(self._dictObj, attr)

class PasswordGen(object):
    def __init__(self, flags):
        self._randSource = RandomDict(flags)
        self._wordCount = flags.w
        self._delimiter = flags.s

    def __iter__(self):
        return self

    def next(self):
        rand_source_iter = iter(self._randSource)
        word_list = [rand_source_iter.next() for i in range(self._wordCount)]
        return self._delimiter.join(word_list)

    def getInfo(self):
        pos = len(self._randSource) ** self._wordCount
        entropy = math.log(pos, 2)
        yrs_to_crack = (pos/(60.*60*24*365*1000000))/2.
        info =  "\nInfo:\n"
        info += "  Entropy: %0.3f bits\n" % entropy
        info += "  Entropy per word: %0.3f bits\n" % (entropy / self._wordCount)
        info += "  At 1 million tries per second, it would take on average %0.3f years to crack.\n" % yrs_to_crack
        return info

class Enumerator(object):
    def __init__(self, gen):
        self.gen = gen
        self.count = 0

    def __iter__(self):
        return self

    def next(self):
        self.count += 1
        return "%d. %s" % (self.count, self.gen.next())

def createParser():
    parser = argparse.ArgumentParser(
            description='Generate an xkcd style password.',
            epilog='http://xkcd.com/936/')
    parser.add_argument('w',
            type=int,
            default=WORDS,
            nargs='?',
            help="The number of words in the password. Defaults to 4.")
    parser.add_argument('-n',
            action='store_true',
            default=False,
            help="Disable printing a newline at the end of the password.\
                  Good for piping to the clipboard.")
    parser.add_argument('-d',
            default=DEFAULT_DICT,
            metavar="DICT_PATH",
            help="The dictionary file. Defaults to %s." % DEFAULT_DICT)
    parser.add_argument('-x',
            action='store_false',
            default=True,
            help="Disable excluding special characters and punctuation.")
    parser.add_argument('-i',
            action='store_true',
            default=False,
            help="Enable showing password information (entropy, etc).")
    parser.add_argument('-s',
            default='-',
            metavar="SEPARATOR",
            help="Delimit words with a given character/string.")
    parser.add_argument('-l',
            default=100,
            type=int,
            metavar="LENGTH",
            help="The maximum word length. Words must be at or below this length.")
    parser.add_argument('-c',
            default=1,
            type=int,
            metavar="COUNT",
            help="Number of passwords to generate. Defaults to 1.")
    parser.add_argument('-v',
            action='store_true',
            default=False,
            help="Display version information.")
    return parser

def main():
    parser = createParser()
    flags = parser.parse_args()
    if flags.v:
        print "Version: " + VERSION_NUMBER
        return
    pGen = iter(PasswordGen(flags))
    # Print the password without adding a \n or space.
    password_count = int(flags.c)
    if password_count == 1:
        sys.stdout.write(pGen.next())
    elif password_count > 1:
        pGenEnumerate = iter(Enumerator(pGen))
        for i in xrange(password_count):
            sys.stdout.write(pGenEnumerate.next() + ("\n" if i != flags.c - 1 else ""))
    else:
        sys.stderr.write("ERROR: Password count is less than 1.\n")
    # Print newline if newline not disabled.
    if not flags.n:
        print ""
    # Make sure the password gets printed before info.
    sys.stdout.flush()
    if flags.i:
        sys.stderr.write(pGen.getInfo())

if __name__ == "__main__":
    main()
<commit_msg>Adds configuration file support. Almost working.<commit_after>#!/usr/bin/python -O

import random
import math
import argparse
import os
import sys
from pkg_resources import resource_filename

# The dictionary bundled with the script. It must be in the same
# dir as the script.
DEFAULT_DICT =  resource_filename(__name__, 'dict.txt')
# Uncomment to use the system dictionary as the default.
#DEFAULT_DICT="/usr/share/dict/words"
WORDS = 4
BAD_CH_LIST = ['\'']
VERSION_NUMBER="0.1.1"

class WordValidator(object):
    """Validates words from the dictionary file, when the file is loaded
       into memory.
    """
    def __init__(self, flags):
        self._badChars = set(BAD_CH_LIST)
        self._maxLen = flags.l
        self._skipBad = flags.x

    def isValidWord(self, word):
        """Returns True if the word is valid. False otherwise.
        """
        if len(word) > self._maxLen or len(word) == 0:
            return False
        if self._skipBad and self._hasBadChar(word):
            return False
        return True

    def _hasBadChar(self, word):
        """Return true if word has a 'bad' char.
        """
        for char in word:
            if char in self._badChars:
                return True

class Dictionary(object):
    def __init__(self, flags):
        self._dictLen = 0
        self._wordValidator = WordValidator(flags)
        self.dict_path = flags.d

    def _openDict(self):
        try:
            dict_file = open(self.dict_path)
        except IOError as e:
            error_msg =  "There was a problem opening the dictionary file '%s': " % self.dict_path
            error_msg += str(e.args[1]) + "\n"
            sys.stderr.write(error_msg)
            exit(1)
        return dict_file

class DictionaryList(Dictionary):
    """List-like object that returns words from a dictionary file.
    """
    def __init__(self, flags):
        super(DictionaryList, self).__init__(flags)
        self._loadDict(flags.d)

    def __getitem__(self, key):
        return self._dictList[key]

    def __len__(self):
        return self._dictLen

    def _loadDict(self, path):
        """Load every word from dict_file that passes the isValidWord test.
        """
        self._dictList = []
        dict_file = self._openDict()
        for line in dict_file:
            word = line.strip()
            if self._wordValidator.isValidWord(word):
                self._dictList.append(word)
        self._dictLen = len(self._dictList)
        dict_file.close()

class RandomDict(object):
    """Iterator that returns on random word per iteration.
    """
    def __init__(self, flags):
        self._dictObj = DictionaryList(flags)
        self._rng = random.SystemRandom()

    def __iter__(self):
        return self

    def __len__(self):
        return len(self._dictObj)

    def next(self):
        return self._dictObj[self._rng.randint(0, len(self) - 1)]

    def __getattr__(self, attr):
        return getattr(self._dictObj, attr)

class PasswordGen(object):
    """Iterator that returns one password per iteration.
    """
    def __init__(self, flags):
        self._randSource = RandomDict(flags)
        self._wordCount = flags.w
        self._delimiter = flags.s

    def __iter__(self):
        return self

    def next(self):
        rand_source_iter = iter(self._randSource)
        word_list = [rand_source_iter.next() for i in range(self._wordCount)]
        return self._delimiter.join(word_list)

    def getInfo(self):
        pos = len(self._randSource) ** self._wordCount
        entropy = math.log(pos, 2)
        yrs_to_crack = (pos/(60.*60*24*365*1000000))/2.
        info =  "\nInfo:\n"
        info += "  Entropy: %0.3f bits\n" % entropy
        info += "  Entropy per word: %0.3f bits\n" % (entropy / self._wordCount)
        info += "  At 1 million tries per second, it would take on average %0.3f years to crack.\n" % yrs_to_crack
        return info

class Enumerator(object):
    """Accepts an iterator and prepends 1., 2., 3., etc to the beginning
       of each item returned from the generator.
    """
    def __init__(self, gen):
        self.gen = gen
        self.count = 0

    def __iter__(self):
        return self

    def next(self):
        self.count += 1
        return "%d. %s" % (self.count, self.gen.next())

class ArgDict(object):
    """Aggregates several Namespace objects, and ducktypes as a Namespace
       object. Looking up an attr looks up the attr in all the Namespaces and
       returns the first which isn't the default value. Otherwise, it returns
       the default.
    """
    def __init__(self, default_dict, *namespaces):
        """default_dict: Dict mapping attr names to default values.
           namespaces: List of Namespace objects from highest precedence
            to lowest.
        """
        self.default_dict = default_dict
        self.namespaces = namespaces

    def __getattribute__(self, name):
        """Lookup 'name' in each namespace object, and return the first value
           that's not the default. Otherwise, return the default.
        """
        for namespace in object.__getattribute__(self, "namespaces"):
            if (
                    hasattr(namespace,name) and
                    getattr(namespace, name) !=
                        object.__getattribute__(self,"default_dict")[name]):
                return getattr(namespace, name)
        return object.__getattribute__(self,"default_dict")[name]

# Default values for command line flags.
flag_defaults = {
    'w': WORDS,
    'n':False,
    'd':DEFAULT_DICT,
    'x':True,
    'i':False,
    's':'-',
    'l':100,
    'c':1,
    'ignore':False,
    'v':False,
    'config':"~/.xkpa"
    }

def createParser():
    """Create the parser for the command line flagsi
    """
    parser = argparse.ArgumentParser(
            description='Generate an xkcd style password.',
            epilog='http://xkcd.com/936/')
    parser.add_argument('w',
            type=int,
            default=flag_defaults['w'],
            nargs='?',
            help="The number of words in the password. Defaults to 4.")
    parser.add_argument('-n',
            action='store_true',
            default=flag_defaults['n'],
            help="Disable printing a newline at the end of the password.\
                  Good for piping to the clipboard.")
    parser.add_argument('-d',
            default=flag_defaults['d'],
            metavar="DICT_PATH",
            help="The dictionary file. Defaults to %s." % DEFAULT_DICT)
    parser.add_argument('-x',
            action='store_false',
            default=flag_defaults['x'],
            help="Disable excluding special characters and punctuation.")
    parser.add_argument('-i',
            action='store_true',
            default=flag_defaults['i'],
            help="Enable showing password information (entropy, etc).")
    parser.add_argument('-s',
            default=flag_defaults['s'],
            metavar="SEPARATOR",
            help="Delimit words with a given character/string.")
    parser.add_argument('-l',
            default=flag_defaults['l'],
            type=int,
            metavar="LENGTH",
            help="The maximum word length. Words must be at or below this length.")
    parser.add_argument('-c',
            default=flag_defaults['c'],
            type=int,
            metavar="COUNT",
            help="Number of passwords to generate. Defaults to 1.")
    parser.add_argument('--config',
            metavar="CONFIG_PATH",
            default=flag_defaults['config'],
            help="Path to config file. Defaults to %s." % flag_defaults['config'])
    parser.add_argument('--ignore',
            action='store_true',
            default=flag_defaults['ignore'],
            help="Ignore the options in the config file.")
    parser.add_argument('-v',
            action='store_true',
            default=flag_defaults['v'],
            help="Display version information.")
    return parser

def strip_quotes(to_strip):
    """If 'to_strip' is surrounded by single or double quotes, return
       the same string with the quotes stripped off.
    """
    if len(to_strip) < 2:
        return to_strip
    elif (
            to_strip[0] == to_strip[-1] == "'" or
            to_strip[0] == to_strip[-1] == '"'):
        return to_strip[1:len(to_strip)-1]
    else:
        return to_strip

def main():
    parser = createParser()
    flags = parser.parse_args()
    if not flags.ignore:
        config_path = os.path.expanduser("~/.xkpa")
        if os.path.isfile(config_path):
            with open(config_path) as config_file:
                options_list = config_file.read().strip().split()
            options_list = map(strip_quotes, options_list)
            config_flags = parser.parse_args(options_list)
            flags = ArgDict(flag_defaults, flags, config_flags)

    if flags.v:
        print "Version: " + VERSION_NUMBER
        return

    pGen = iter(PasswordGen(flags))
    # Print the password without adding a \n or space.
    password_count = int(flags.c)
    if password_count == 1:
        sys.stdout.write(pGen.next())
    elif password_count > 1:
        pGenEnumerate = iter(Enumerator(pGen))
        for i in xrange(password_count):
            sys.stdout.write(pGenEnumerate.next() + ("\n" if i != flags.c - 1 else ""))
    else:
        sys.stderr.write("ERROR: Password count is less than 1.\n")
    # Print newline if newline not disabled.
    if not flags.n:
        print ""

    # Make sure the password gets printed before info.
    sys.stdout.flush()
    if flags.i:
        sys.stderr.write(pGen.getInfo())

if __name__ == "__main__":
    main()
<|endoftext|>


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

import os
import feedparser
import urllib
import json
import random
import wikipedia

#######################################################
#########        Funciones principales       ##########
#######################################################

def comprobar_update(rb):
    file = rb.ruta_tmp+"/update"
    fileexists = os.path.isfile(file)
    if fileexists:
        cmd = 'rm '+rb.ruta_tmp+'/update'
        os.system(cmd)
        return True
    else:
        return False

def update_bot(rb):
    ruta = rb.ruta_bot.rstrip('raspibot')
    cmd = "cd "+ruta
    os.system(cmd)
    cmd = 'mv '+rb.ruta_bot+' '+ruta+'raspibot-bak'
    os.system(cmd)
    os.system('git clone https://github.com/alhenx/raspibot.git --quiet')
    cmd = 'cp -r '+ruta+'raspibot-bak/config '+rb.ruta_bot
    os.system(cmd)
    cmd = 'cp -r '+ruta+'raspibot-bak/tmp '+rb.ruta_bot
    os.system(cmd)
    cmd = 'cd '+rb.ruta_bot
    os.system(cmd)
    os.system('echo "#!/bin/bash" > raspibot.sh')
    os.system('echo "python $installdir/raspibot/raspibot.py &" >> raspibot.sh')
    os.system('sudo chmod a+x raspibot.sh')
    cmd = 'rm -rf '+ruta+'raspibot-bak'
    os.system(cmd)
    os.system('sudo /usr/local/bin/restartraspibot.sh')
    cmd = '> '+rb.ruta_tmp+'/update'
    os.system(cmd)

def comprobar_version(rb):
    url = "https://raw.githubusercontent.com/alhenx/raspibot/master/version"
    data = urllib.request.urlopen(url)
    data = data.read().decode("UTF-8")
    version = open(rb.ruta_bot+'/version')
    version = version.read()
    if str(data) != version:
        return False
    else:
        return True

def modificar_torrent(text,rb):
    if text == 'on':
        rb.torrent_v = True
        cmd = '> '+rb.ruta_tmp+'/torrent'
        os.system(cmd)
        return "Aviso de torrent activado"
    if text == 'off':
        rb.torrent_v = False
        cmd = 'rm '+rb.ruta_tmp+'/torrent'
        os.system(cmd)
        return "Aviso de torrent desactivado"
    if text == 'status':
        if rb.torrent_v == True:
            return "El aviso de torrent esta activado"
        else:
            return "El aviso de torrent esta desactivado"

def modificar_ambilight(text,rb):
    if text == 'on':
        if rb.ruta_ambi != "NOPE":
            rb.ambi_v = True
            cmd = '> '+rb.ruta_tmp+'/ambilight'
            os.system(cmd)
            cmd = rb.ruta_ambi+' -o saturation=2.0 -p 100 -f /dev/null > /dev/null'
            os.system(cmd)
            return "Ambilight activado"
        else:
            return "No dispone de sistema ambilight"
    if text == 'off':
        if rb.ruta_ambi != "NOPE":
            rb.ambi_v = False
            cmd = 'rm '+rb.ruta_tmp+'/ambilight'
            os.system(cmd)
            pid = os.popen('pidof boblight-dispmanx').read()
            cmd ='kill -9 '+pid
            os.system(cmd)
            return "Ambilight desactivado"
        else:
            return "No dispone de sistema ambilight"
    if text == 'status':
        if rb.ambi_v == True:
            return "Ambilight esta activado"
        else:
            return "Ambilight esta desactivado"

def modificar_rss(text,rb):
    if text == 'on':
        rb.rss_v = True
        cmd = '> '+rb.ruta_tmp+'/rss'
        os.system(cmd)
        return "RSS activado"
    if text == 'off':
        rb.rss_v = False
        cmd = 'rm '+rb.ruta_tmp+'/rss'
        os.system(cmd)
        return "RSS desactivado"
    if text == 'add':
        rb.rssadd = True
        return 'Introduzca el nuevo feed'
    if text == 'del':
        rb.rssdel = True
        return 'Introduzca el numero que desea eliminar, si no los conoce use /rss list'
    if text == 'list':
        response = ''
        c = 1
        fpath = rb.ruta_config+'/listarss'
        if os.path.isfile(fpath) and os.path.getsize(fpath) > 0:
            archivo = open(rb.ruta_config+'/listarss', 'r')
            for line in archivo.readlines():
                response += str(c)+' '+line
                c += 1
            archivo.close()
        else:
            response = "Lista vacia"
        return response
    if text == 'status':
        if rb.rss_v == True:
            return "RSS esta activado"
        else:
            return "RSS esta desactivado"

def funcion_rss(rb):
    del rb.listarss[:]
    for i in range(0,len(rb.listaurls)):
        rb.listarss.append(feedparser.parse(rb.listaurls[i]))
    for i in range(0,len(rb.listarss)):
        if rb.listarss[i].entries[0].title not in rb.bdrss:
            rb.bdrss.append(rb.listarss[i].entries[0].title)
            return "Noticia nueva:\n"+ rb.listarss[i].entries[0].title + ": \n"+ rb.listarss[i].entries[0].link

def comprobar_rss(text,rb):
    if rb.rssadd == True:
        aux = feedparser.parse(text)
        c = False
        fpath = rb.ruta_config+'/listarss'
        if not os.path.isfile(fpath):
            cmd = '> '+rb.ruta_tmp+'/listarss'
            os.system(cmd)
        try:
            response = "El feed "+aux.feed.title+" ha sido introducido"
            rb.listaurls.append(text)
            outfile = open(rb.ruta_config+'/listarss', 'a')
            outfile.write(text+'\n')
            outfile.close()
            rb.listarss.append(aux)
            rb.bdrss.append(rb.listarss[-1].entries[0].title)
        except:
            response = "[Error] El feed no existe"
        rb.rssadd = False
    if rb.rssdel == True:
        try:
            f = open(rb.ruta_config+'/listarss',"r")
            lineas = f.readlines()
            f.close()
            f = open(rb.ruta_config+'/listarss',"w")
            c = 1
            for linea in lineas:
                cont=str(c)
                if cont!=text:
                    f.write(linea)
                c += 1
            f.close()
            del rb.listaurls[int(text)-1]
            response = "Feed borrado"
        except:
            response = "[Error] Feed inexistente"
        rb.rssdel = False
    return response

def funcion_google(text):
    if(text[0] == '-'):
        num = text[1]
    else:
        num = 5
        lista = ''
        query = text.encode('utf-8')
        query = urllib.parse.urlencode ( { 'q' : query } )
        url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=%s&%s' % (num,query)
        search_response = urllib.request.urlopen(url)
        search_results = search_response.read()
        results = json.loads(search_results.decode("UTF-8"))
        data = results[ 'responseData' ]
        hits = data['results']
        for h in hits: lista+=(h['url']+'\n')
        return lista

def funcion_img(text,ruta_img):
    lista = []
    query = text.encode('utf-8')
    query = urllib.parse.urlencode ( { 'q' : query } )
    url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&%s' % query
    search_response = urllib.request.urlopen(url)
    search_results = search_response.read()
    results = json.loads(search_results.decode("UTF-8"))
    data = results[ 'responseData' ]
    hits = data['results']
    for h in hits: lista.append(h['url'])
    f = open(ruta_img+'/image.jpg','wb')
    f.write(urllib.request.urlopen(random.choice(lista)).read())
    f.close()

def funcion_wiki(text):
    wikipedia.set_lang("es")
    return wikipedia.page(text).url

#######################################################

#######################################################
#########          Funciones Torrent         ##########
#######################################################

def check_file(filetorrent):
  if isfile(filetorrent):
    wcout = count_filelines(filetorrent)
    if wcout > 1:
      message="Torrents completados:\n"+cat_file(filetorrent)
    else :
      message="Torrents completado:\n"+cat_file(filetorrent)
    newwcout = count_filelines(filetorrent)
    if wcout < newwcout :
    #Si se han añadido más lineas desde la ultima vez
        check_file()
    rm_file(filetorrent)
    return message

def rm_file(rmfile):
    if isfile(rmfile):
        rm = "rm "+rmfile+" > /dev/null 2>&1"
        os.system(rm)

def count_filelines(wcfile):
    wc = "cat "+wcfile+" | wc -l"
    return int(os.popen(wc).read())

def cat_file(catfile):
    cat = "cat "+catfile
    return os.popen(cat).read()

def isfile(fileexists):
    return os.path.isfile(fileexists)

#######################################################<commit_msg>Update<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import feedparser
import urllib
import json
import random
import wikipedia

#######################################################
#########        Funciones principales       ##########
#######################################################

def comprobar_update(rb):
    file = rb.ruta_tmp+"/update"
    fileexists = os.path.isfile(file)
    if fileexists:
        cmd = 'rm '+rb.ruta_tmp+'/update'
        os.system(cmd)
        return True
    else:
        return False

def update_bot(rb):
    ruta = '/opt/raspibot-setup/'
    cmd = "cd "+ruta
    os.system(cmd)
    cmd = 'mv '+rb.ruta_bot+' '+ruta+'raspibot-bak'
    os.system(cmd)
    os.system('git clone https://github.com/alhenx/raspibot.git --quiet')
    cmd = 'cp -r '+ruta+'raspibot-bak/config '+rb.ruta_bot
    os.system(cmd)
    cmd = 'cp -r '+ruta+'raspibot-bak/tmp '+rb.ruta_bot
    os.system(cmd)
    cmd = 'cd '+rb.ruta_bot
    os.system(cmd)
    os.system('echo "#!/bin/bash" > raspibot.sh')
    os.system('echo "python $installdir/raspibot/raspibot.py &" >> raspibot.sh')
    os.system('sudo chmod a+x raspibot.sh')
    cmd = 'rm -rf '+ruta+'raspibot-bak'
    os.system(cmd)
    os.system('sudo /usr/local/bin/restartraspibot.sh')
    cmd = '> '+rb.ruta_tmp+'/update'
    os.system(cmd)

def comprobar_version(rb):
    url = "https://raw.githubusercontent.com/alhenx/raspibot/master/version"
    data = urllib.request.urlopen(url)
    data = data.read().decode("UTF-8")
    version = open(rb.ruta_bot+'/version')
    version = version.read()
    if str(data) != version:
        return False
    else:
        return True

def modificar_torrent(text,rb):
    if text == 'on':
        rb.torrent_v = True
        cmd = '> '+rb.ruta_tmp+'/torrent'
        os.system(cmd)
        return "Aviso de torrent activado"
    if text == 'off':
        rb.torrent_v = False
        cmd = 'rm '+rb.ruta_tmp+'/torrent'
        os.system(cmd)
        return "Aviso de torrent desactivado"
    if text == 'status':
        if rb.torrent_v == True:
            return "El aviso de torrent esta activado"
        else:
            return "El aviso de torrent esta desactivado"

def modificar_ambilight(text,rb):
    if text == 'on':
        if rb.ruta_ambi != "NOPE":
            rb.ambi_v = True
            cmd = '> '+rb.ruta_tmp+'/ambilight'
            os.system(cmd)
            cmd = rb.ruta_ambi+' -o saturation=2.0 -p 100 -f /dev/null > /dev/null'
            os.system(cmd)
            return "Ambilight activado"
        else:
            return "No dispone de sistema ambilight"
    if text == 'off':
        if rb.ruta_ambi != "NOPE":
            rb.ambi_v = False
            cmd = 'rm '+rb.ruta_tmp+'/ambilight'
            os.system(cmd)
            pid = os.popen('pidof boblight-dispmanx').read()
            cmd ='kill -9 '+pid
            os.system(cmd)
            return "Ambilight desactivado"
        else:
            return "No dispone de sistema ambilight"
    if text == 'status':
        if rb.ambi_v == True:
            return "Ambilight esta activado"
        else:
            return "Ambilight esta desactivado"

def modificar_rss(text,rb):
    if text == 'on':
        rb.rss_v = True
        cmd = '> '+rb.ruta_tmp+'/rss'
        os.system(cmd)
        return "RSS activado"
    if text == 'off':
        rb.rss_v = False
        cmd = 'rm '+rb.ruta_tmp+'/rss'
        os.system(cmd)
        return "RSS desactivado"
    if text == 'add':
        rb.rssadd = True
        return 'Introduzca el nuevo feed'
    if text == 'del':
        rb.rssdel = True
        return 'Introduzca el numero que desea eliminar, si no los conoce use /rss list'
    if text == 'list':
        response = ''
        c = 1
        fpath = rb.ruta_config+'/listarss'
        if os.path.isfile(fpath) and os.path.getsize(fpath) > 0:
            archivo = open(rb.ruta_config+'/listarss', 'r')
            for line in archivo.readlines():
                response += str(c)+' '+line
                c += 1
            archivo.close()
        else:
            response = "Lista vacia"
        return response
    if text == 'status':
        if rb.rss_v == True:
            return "RSS esta activado"
        else:
            return "RSS esta desactivado"

def funcion_rss(rb):
    del rb.listarss[:]
    for i in range(0,len(rb.listaurls)):
        rb.listarss.append(feedparser.parse(rb.listaurls[i]))
    for i in range(0,len(rb.listarss)):
        if rb.listarss[i].entries[0].title not in rb.bdrss:
            rb.bdrss.append(rb.listarss[i].entries[0].title)
            return "Noticia nueva:\n"+ rb.listarss[i].entries[0].title + ": \n"+ rb.listarss[i].entries[0].link

def comprobar_rss(text,rb):
    if rb.rssadd == True:
        aux = feedparser.parse(text)
        c = False
        fpath = rb.ruta_config+'/listarss'
        if not os.path.isfile(fpath):
            cmd = '> '+rb.ruta_tmp+'/listarss'
            os.system(cmd)
        try:
            response = "El feed "+aux.feed.title+" ha sido introducido"
            rb.listaurls.append(text)
            outfile = open(rb.ruta_config+'/listarss', 'a')
            outfile.write(text+'\n')
            outfile.close()
            rb.listarss.append(aux)
            rb.bdrss.append(rb.listarss[-1].entries[0].title)
        except:
            response = "[Error] El feed no existe"
        rb.rssadd = False
    if rb.rssdel == True:
        try:
            f = open(rb.ruta_config+'/listarss',"r")
            lineas = f.readlines()
            f.close()
            f = open(rb.ruta_config+'/listarss',"w")
            c = 1
            for linea in lineas:
                cont=str(c)
                if cont!=text:
                    f.write(linea)
                c += 1
            f.close()
            del rb.listaurls[int(text)-1]
            response = "Feed borrado"
        except:
            response = "[Error] Feed inexistente"
        rb.rssdel = False
    return response

def funcion_google(text):
    if(text[0] == '-'):
        num = text[1]
    else:
        num = 5
        lista = ''
        query = text.encode('utf-8')
        query = urllib.parse.urlencode ( { 'q' : query } )
        url = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=%s&%s' % (num,query)
        search_response = urllib.request.urlopen(url)
        search_results = search_response.read()
        results = json.loads(search_results.decode("UTF-8"))
        data = results[ 'responseData' ]
        hits = data['results']
        for h in hits: lista+=(h['url']+'\n')
        return lista

def funcion_img(text,ruta_img):
    lista = []
    query = text.encode('utf-8')
    query = urllib.parse.urlencode ( { 'q' : query } )
    url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&%s' % query
    search_response = urllib.request.urlopen(url)
    search_results = search_response.read()
    results = json.loads(search_results.decode("UTF-8"))
    data = results[ 'responseData' ]
    hits = data['results']
    for h in hits: lista.append(h['url'])
    f = open(ruta_img+'/image.jpg','wb')
    f.write(urllib.request.urlopen(random.choice(lista)).read())
    f.close()

def funcion_wiki(text):
    wikipedia.set_lang("es")
    return wikipedia.page(text).url

#######################################################

#######################################################
#########          Funciones Torrent         ##########
#######################################################

def check_file(filetorrent):
  if isfile(filetorrent):
    wcout = count_filelines(filetorrent)
    if wcout > 1:
      message="Torrents completados:\n"+cat_file(filetorrent)
    else :
      message="Torrents completado:\n"+cat_file(filetorrent)
    newwcout = count_filelines(filetorrent)
    if wcout < newwcout :
    #Si se han añadido más lineas desde la ultima vez
        check_file()
    rm_file(filetorrent)
    return message

def rm_file(rmfile):
    if isfile(rmfile):
        rm = "rm "+rmfile+" > /dev/null 2>&1"
        os.system(rm)

def count_filelines(wcfile):
    wc = "cat "+wcfile+" | wc -l"
    return int(os.popen(wc).read())

def cat_file(catfile):
    cat = "cat "+catfile
    return os.popen(cat).read()

def isfile(fileexists):
    return os.path.isfile(fileexists)

#######################################################<|endoftext|>


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

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
import os

from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

# This value changes every day with an automatic CL. It can be modified in code
# via `forward_compatibility_horizon()` or with the environment variable
# TF_FORWARD_COMPATIBILITY_DELTA_DAYS, which is added to the compatibility date.
_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 7, 22)

_FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = False
_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME = "TF_FORWARD_COMPATIBILITY_DELTA_DAYS"

def _get_forward_compatibility_date():
  date = _FORWARD_COMPATIBILITY_HORIZON
  delta_days = os.getenv(_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME)
  if delta_days is not None and not _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN:
    return date + datetime.timedelta(days=int(delta_days))
  else:
    return date

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibiltiy, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _get_forward_compatibility_date() > datetime.date(year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Yields:
    Nothing.
  """
  global _FORWARD_COMPATIBILITY_HORIZON
  global _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN
  try:
    old_compat_date = _FORWARD_COMPATIBILITY_HORIZON
    _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = True
    _FORWARD_COMPATIBILITY_HORIZON = datetime.date(year, month, day)
    yield
  finally:
    _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = False
    _FORWARD_COMPATIBILITY_HORIZON = old_compat_date
<commit_msg>compat: Update forward compatibility horizon to 2019-07-23<commit_after># Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Utilities for API compatibility between TensorFlow release versions.

See [Version
Compatibility](https://tensorflow.org/guide/version_compat#backward_forward)
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import datetime
import os

from tensorflow.python.util import tf_contextlib
from tensorflow.python.util.tf_export import tf_export

# This value changes every day with an automatic CL. It can be modified in code
# via `forward_compatibility_horizon()` or with the environment variable
# TF_FORWARD_COMPATIBILITY_DELTA_DAYS, which is added to the compatibility date.
_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 7, 23)

_FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = False
_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME = "TF_FORWARD_COMPATIBILITY_DELTA_DAYS"

def _get_forward_compatibility_date():
  date = _FORWARD_COMPATIBILITY_HORIZON
  delta_days = os.getenv(_FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME)
  if delta_days is not None and not _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN:
    return date + datetime.timedelta(days=int(delta_days))
  else:
    return date

@tf_export("compat.forward_compatible")
def forward_compatible(year, month, day):
  """Return true if the forward compatibility window has expired.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  Forward-compatibility refers to scenarios where the producer of a TensorFlow
  model (a GraphDef or SavedModel) is compiled against a version of the
  TensorFlow library newer than what the consumer was compiled against. The
  "producer" is typically a Python program that constructs and trains a model
  while the "consumer" is typically another program that loads and serves the
  model.

  TensorFlow has been supporting a 3 week forward-compatibility window for
  programs compiled from source at HEAD.

  For example, consider the case where a new operation `MyNewAwesomeAdd` is
  created with the intent of replacing the implementation of an existing Python
  wrapper - `tf.add`.  The Python wrapper implementation should change from
  something like:

  ```python
  def add(inputs, name=None):
    return gen_math_ops.add(inputs, name)
  ```

  to:

  ```python
  from tensorflow.python.compat import compat

  def add(inputs, name=None):
    if compat.forward_compatible(year, month, day):
      # Can use the awesome new implementation.
      return gen_math_ops.my_new_awesome_add(inputs, name)
    # To maintain forward compatibiltiy, use the old implementation.
    return gen_math_ops.add(inputs, name)
  ```

  Where `year`, `month`, and `day` specify the date beyond which binaries
  that consume a model are expected to have been updated to include the
  new operations. This date is typically at least 3 weeks beyond the date
  the code that adds the new operation is committed.

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Returns:
    True if the caller can expect that serialized TensorFlow graphs produced
    can be consumed by programs that are compiled with the TensorFlow library
    source code after (year, month, day).
  """
  return _get_forward_compatibility_date() > datetime.date(year, month, day)

@tf_export("compat.forward_compatibility_horizon")
@tf_contextlib.contextmanager
def forward_compatibility_horizon(year, month, day):
  """Context manager for testing forward compatibility of generated graphs.

  See [Version
  compatibility](https://tensorflow.org/guide/version_compat#backward_forward).

  To ensure forward compatibility of generated graphs (see `forward_compatible`)
  with older binaries, new features can be gated with:

  ```python
  if compat.forward_compatible(year=2018, month=08, date=01):
    generate_graph_with_new_features()
  else:
    generate_graph_so_older_binaries_can_consume_it()
  ```

  However, when adding new features, one may want to unittest it before
  the forward compatibility window expires. This context manager enables
  such tests. For example:

  ```python
  from tensorflow.python.compat import compat

  def testMyNewFeature(self):
    with compat.forward_compatibility_horizon(2018, 08, 02):
       # Test that generate_graph_with_new_features() has an effect
  ```

  Args:
    year:  A year (e.g., 2018). Must be an `int`.
    month: A month (1 <= month <= 12) in year. Must be an `int`.
    day:   A day (1 <= day <= 31, or 30, or 29, or 28) in month. Must be an
      `int`.

  Yields:
    Nothing.
  """
  global _FORWARD_COMPATIBILITY_HORIZON
  global _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN
  try:
    old_compat_date = _FORWARD_COMPATIBILITY_HORIZON
    _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = True
    _FORWARD_COMPATIBILITY_HORIZON = datetime.date(year, month, day)
    yield
  finally:
    _FORWARD_COMPATIBILITY_HORIZON_OVERRIDDEN = False
    _FORWARD_COMPATIBILITY_HORIZON = old_compat_date
<|endoftext|>