<commit_before># -*- coding: iso-8859-1 -*-
from DriverInterface import DriverInterface

import serial
import sys

def debugEnabled( *args ):
    print >>sys.stderr, " ".join( map(str, args) )

def debugDisabled( *args ):
    pass

debug = debugDisabled

class PrinterException(Exception):
    pass

class UnknownServerError(PrinterException):
    errorNumber = 1

class ComunicationError(PrinterException):
    errorNumber = 2

class PrinterStatusError(PrinterException):
    errorNumber = 3

class FiscalStatusError(PrinterException):
    errorNumber = 4

ServerErrors = [UnknownServerError, ComunicationError, PrinterStatusError, FiscalStatusError]

class ProxyError(PrinterException):
    errorNumber = 5

class FiscalPrinterDriver(DriverInterface):
    WAIT_TIME = 10
    RETRIES = 4
    WAIT_CHAR_TIME = 0.1
    NO_REPLY_TRIES = 200

    def __init__( self, path, speed = 9600 ):
        self._serialPort = serial.Serial( port = path, timeout = None, baudrate = speed )
        self._initSequenceNumber()

    def close( self ):
        try:
            self._serialPort.close()
        except:
            pass
        del self._serialPort

    def sendCommand( self, commandNumber, fields, skipStatusErrors = False ):
        message = chr(0x02) + chr( self._sequenceNumber ) + chr(commandNumber)
        if fields:
            message += chr(0x1c)
        message += chr(0x1c).join( fields )
        message += chr(0x03)
        checkSum = sum( [ord(x) for x in message ] )
        checkSumHexa = ("0000" + hex(checkSum)[2:])[-4:].upper()
        message += checkSumHexa
        reply = self._sendMessage( message )
        self._incrementSequenceNumber()
        return self._parseReply( reply, skipStatusErrors )

    def _write( self, s ):
        debug( "_write", ", ".join( [ "%x" % ord(c) for c in s ] ) )
        print("Escribiendo ...")
        print(s.encode('ascii', 'replace'))
        print(s.encode())
        self._serialPort.write( s.encode() )

    def _read( self, count ):
        ret = self._serialPort.read( count )
        debug( "_read", ", ".join( [ "%x" % ord(c) for c in ret ] ) )
        return ret

    def __del__( self ):
        if hasattr(self, "_serialPort" ):
            try:
                self.close()
            except:
                pass

    def _parseReply( self, reply, skipStatusErrors ):
        r = reply[4:-1] # Saco STX <Nro Seq> <Nro Comando> <Sep> ... ETX
        fields = r.split( chr(28) )
        printerStatus = fields[0]
        fiscalStatus = fields[1]
        if not skipStatusErrors:
            self._parsePrinterStatus( printerStatus )
            self._parseFiscalStatus( fiscalStatus )
        return fields

    def _parsePrinterStatus( self, printerStatus ):
        x = int( printerStatus, 16 )
        for value, message in self.printerStatusErrors:
            if (value & x) == value:
                raise PrinterStatusError, message

    def _parseFiscalStatus( self, fiscalStatus ):
        x = int( fiscalStatus, 16 )
        for value, message in self.fiscalStatusErrors:
            if (value & x) == value:
                raise FiscalStatusError, message

    def _checkReplyBCC( self, reply, bcc ):
        debug( "reply", reply, [ord(x) for x in reply] )
        checkSum = sum( [ord(x) for x in reply ] )
        debug( "checkSum", checkSum )
        checkSumHexa = ("0000" + hex(checkSum)[2:])[-4:].upper()
        debug( "checkSumHexa", checkSumHexa )
        debug( "bcc", bcc )
        return checkSumHexa == bcc.upper()

<commit_msg>ascii charcode para impresora fiscal<commit_after># -*- coding: iso-8859-1 -*-
from DriverInterface import DriverInterface

import serial
import sys

def debugEnabled( *args ):
    print >>sys.stderr, " ".join( map(str, args) )

def debugDisabled( *args ):
    pass

debug = debugDisabled

class PrinterException(Exception):
    pass

class UnknownServerError(PrinterException):
    errorNumber = 1

class ComunicationError(PrinterException):
    errorNumber = 2

class PrinterStatusError(PrinterException):
    errorNumber = 3

class FiscalStatusError(PrinterException):
    errorNumber = 4

ServerErrors = [UnknownServerError, ComunicationError, PrinterStatusError, FiscalStatusError]

class ProxyError(PrinterException):
    errorNumber = 5

class FiscalPrinterDriver(DriverInterface):
    WAIT_TIME = 10
    RETRIES = 4
    WAIT_CHAR_TIME = 0.1
    NO_REPLY_TRIES = 200

    def __init__( self, path, speed = 9600 ):
        self._serialPort = serial.Serial( port = path, timeout = None, baudrate = speed )
        self._initSequenceNumber()

    def close( self ):
        try:
            self._serialPort.close()
        except:
            pass
        del self._serialPort

    def sendCommand( self, commandNumber, fields, skipStatusErrors = False ):
        message = chr(0x02) + chr( self._sequenceNumber ) + chr(commandNumber)
        if fields:
            message += chr(0x1c)
        message += chr(0x1c).join( fields )
        message += chr(0x03)
        checkSum = sum( [ord(x) for x in message ] )
        checkSumHexa = ("0000" + hex(checkSum)[2:])[-4:].upper()
        message += checkSumHexa
        reply = self._sendMessage( message )
        self._incrementSequenceNumber()
        return self._parseReply( reply, skipStatusErrors )

    def _write( self, s ):
        debug( "_write", ", ".join( [ "%x" % ord(c) for c in s ] ) )
        print("Escribiendo ...")
        print(s.encode('ascii', 'ignore'))
        print("*-*-**-*-**-*-**-*-*")
        print(s.encode())
        self._serialPort.write( s.encode() )

    def _read( self, count ):
        ret = self._serialPort.read( count )
        debug( "_read", ", ".join( [ "%x" % ord(c) for c in ret ] ) )
        return ret

    def __del__( self ):
        if hasattr(self, "_serialPort" ):
            try:
                self.close()
            except:
                pass

    def _parseReply( self, reply, skipStatusErrors ):
        r = reply[4:-1] # Saco STX <Nro Seq> <Nro Comando> <Sep> ... ETX
        fields = r.split( chr(28) )
        printerStatus = fields[0]
        fiscalStatus = fields[1]
        if not skipStatusErrors:
            self._parsePrinterStatus( printerStatus )
            self._parseFiscalStatus( fiscalStatus )
        return fields

    def _parsePrinterStatus( self, printerStatus ):
        x = int( printerStatus, 16 )
        for value, message in self.printerStatusErrors:
            if (value & x) == value:
                raise PrinterStatusError, message

    def _parseFiscalStatus( self, fiscalStatus ):
        x = int( fiscalStatus, 16 )
        for value, message in self.fiscalStatusErrors:
            if (value & x) == value:
                raise FiscalStatusError, message

    def _checkReplyBCC( self, reply, bcc ):
        debug( "reply", reply, [ord(x) for x in reply] )
        checkSum = sum( [ord(x) for x in reply ] )
        debug( "checkSum", checkSum )
        checkSumHexa = ("0000" + hex(checkSum)[2:])[-4:].upper()
        debug( "checkSumHexa", checkSumHexa )
        debug( "bcc", bcc )
        return checkSumHexa == bcc.upper()

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright (c) 2008-2009 Benoit Chesneau <benoitc@e-engura.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# code heavily inspired from django.forms.models
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#    1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#    3. Neither the name of Django nor the names of its contributors may be used
#       to endorse or promote products derived from this software without
#       specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

""" Implement DocumentForm object. It map Document objects to Form and
works like ModelForm object :

    >>> from couchdbkit.ext.django.forms  import DocumentForm

    # Create the form class.
    >>> class ArticleForm(DocumentForm):
    ...     class Meta:
    ...         model = Article

    # Creating a form to add an article.
    >>> form = ArticleForm()

    # Creating a form to change an existing article.
    >>> article = Article.get(someid)
    >>> form = ArticleForm(instance=article)

The generated Form class will have a form field for every model field.
Each document property has a corresponding default form field:

* StringProperty   ->  CharField,
* IntegerProperty  ->  IntegerField,
* DecimalProperty  ->  DecimalField,
* BooleanProperty  ->  BooleanField,
* FloatProperty    ->  FloatField,
* DateTimeProperty ->  DateTimeField,
* DateProperty     ->  DateField,
* TimeProperty     ->  TimeField

More fields types will be supported soon.
"""

from django.utils.datastructures import SortedDict
from django.forms.util import ErrorList
from django.forms.forms import BaseForm, get_declared_fields, ValidationError
from django.forms.widgets import media_property

from dockit.schema.exceptions import DotPathNotFound

import inspect

def document_to_dict(document, instance, properties=None, exclude=None, dotpath=None):
    """
    Returns a dict containing the data in ``instance`` suitable for passing as
    a Form's ``initial`` keyword argument.

    ``properties`` is an optional list of properties names. If provided,
    only the named properties will be included in the returned dict.

    ``exclude`` is an optional list of properties names. If provided, the named
    properties will be excluded from the returned dict, even if they are listed
    in the ``properties`` argument.
    """
    # avoid a circular import
    data = {}
    if dotpath:
        try:
            src_data = instance.dot_notation(dotpath)
        except DotPathNotFound:
            src_data = dict()
    else:
        src_data = instance

    if src_data is None:
        src_data = dict()

    for prop_name in src_data.keys():
        if properties and not prop_name in properties:
            continue
        if exclude and prop_name in exclude:
            continue
        data[prop_name] = src_data[prop_name]
    return data

def fields_for_document(document, properties=None, exclude=None, form_field_callback=None, dotpath=None):
    """
    Returns a ``SortedDict`` containing form fields for the given document.

    ``properties`` is an optional list of properties names. If provided,
    only the named properties will be included in the returned properties.

    ``exclude`` is an optional list of properties names. If provided, the named
    properties will be excluded from the returned properties, even if
    they are listed in the ``properties`` argument.
    """
    if dotpath:
        field = document._meta.dot_notation_to_field(dotpath)
        fields = field.schema._meta.fields
    else:
        fields = document._meta.fields

    field_list = []

    values = []
    if properties:
        values = [fields[prop] for prop in properties if \
                                                prop in fields]
    else:
        values = fields.values()
        #values.sort(lambda a, b: cmp(a.creation_counter, b.creation_counter))

    for prop in values:
        if properties and not prop.name in properties:
            continue
        if exclude and prop.name in exclude:
            continue

        field = prop.formfield()

        if field and form_field_callback:
            defaults = prop.formfield_kwargs()
            field = form_field_callback(prop, type(field), **defaults)
        if field:
            field_list.append((prop.name, field))
    return SortedDict(field_list)

class DocumentFormOptions(object):
    def __init__(self, options=None):
        self.document = getattr(options, 'document', None)
        self.schema = getattr(options, 'schema', None)
        self.properties = getattr(options, 'properties', None)
        self.exclude = getattr(options, 'exclude', None)
        self.form_field_callback = getattr(options, 'form_field_callback', None)
        self.dotpath = getattr(options, 'dotpath', None)

        #lookup the appropriate schema if none is given
        if self.document and not self.schema:
            if self.dotpath:
                field = self.document._meta.dot_notation_to_field(self.dotpath)
                if hasattr(field, 'schema'):
                    self.schema = field.schema
            else:
                self.schema = self.document

class DocumentFormMetaClass(type):
    def __new__(cls, name, bases, attrs):
        try:
            parents = [b for b in bases if issubclass(b, DocumentForm)]
        except NameError:
            # We are defining ModelForm itself.
            parents = None

        declared_fields = get_declared_fields(bases, attrs, False)
        new_class = super(DocumentFormMetaClass, cls).__new__(cls, name, bases,
                    attrs)

        if not parents:
            return new_class

        if 'media' not in attrs:
            new_class.media = media_property(new_class)

        opts = new_class._meta = DocumentFormOptions(getattr(new_class,
                                                'Meta', None))
        if opts.schema:
            fields = fields_for_document(opts.schema, opts.properties,
                                         opts.exclude, form_field_callback=opts.form_field_callback,)
            # Override default docuemnt fields with any custom declared ones
            # (plus, include all the other declared fields).
            new_class.serialized_fields = fields.keys()
            fields.update(declared_fields)
        elif opts.document: #TODO this should no longer be necessary
            # If a document is defined, extract form fields from it.
            fields = fields_for_document(opts.document, opts.properties,
                                         opts.exclude, form_field_callback=opts.form_field_callback,
                                         dotpath=opts.dotpath)
            # Override default docuemnt fields with any custom declared ones
            # (plus, include all the other declared fields).
            new_class.serialized_fields = fields.keys()
            fields.update(declared_fields)
        else:
            fields = declared_fields

        new_class.declared_fields = declared_fields
        new_class.base_fields = fields
        return new_class

class BaseDocumentForm(BaseForm):
    """ Base Document Form object """

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
            initial=None, error_class=ErrorList, label_suffix=":",
            empty_permitted=False, instance=None, dotpath=None):
        opts = self._meta
        self.dotpath = dotpath or opts.dotpath

        if instance is None:
            self.instance = opts.document()
            object_data = {}
        else:
            self.instance = instance
            object_data = document_to_dict(self._meta.document, instance, opts.properties,
                                        opts.exclude, dotpath=self.dotpath)
        if initial is not None:
            object_data.update(initial)

        super(BaseDocumentForm, self).__init__(data, files, auto_id, prefix,
                                            object_data, error_class,
                                            label_suffix, empty_permitted)
        #print self.data, self.initial, self.instance._primitive_data, self.instance._python_data

    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                arg_spec = inspect.getargspec(field.clean)
                if ((isinstance(arg_spec, tuple) and len(arg_spec[0]) > 2) or #py2.5
                    len(arg_spec.args) > 2): #py2.6+
                    initial = self.initial.get(name, field.initial)
                    if callable(initial):
                        initial = initial()
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError, e:
                self._errors[name] = self.error_class(e.messages)
                if name in self.cleaned_data:
                    del self.cleaned_data[name]

    def _inner_save(self, dynamic=True):
        opts = self._meta
        cleaned_data = self.cleaned_data.copy()

        if self.dotpath:
            obj = None
            try:
                obj = self.instance.dot_notation(self.dotpath)
            except:
                pass
            if obj is None:
                obj = opts.schema()
        else:
            data = self.instance.to_primitive(self.instance)
            obj = opts.schema.to_python(data)

        for prop_name in self.serialized_fields:
            if prop_name in cleaned_data.keys():
                value = cleaned_data.pop(prop_name)
                obj[prop_name] = value

        if dynamic:
            for attr_name in cleaned_data.iterkeys():
                if opts.exclude and attr_name in opts.exclude:
                    continue
                value = cleaned_data[attr_name]
                if value is not None:
                    obj[attr_name] = value
        return obj

    def save(self, commit=True, dynamic=True):
        """
        Saves this ``form``'s cleaned_data into document instance
        ``self.instance``.

        If commit=True, then the changes to ``instance`` will be saved to the
        database. Returns ``instance``.
        """
        #_inner_save wraps it in the schema while we wrap it in the document
        self.target_object = self._inner_save(dynamic)

        if self.dotpath:
            self.instance.dot_notation_set_value(self.dotpath, self.target_object)
        else:
            self.instance = self.target_object

        data = self.instance.to_primitive(self.instance)
        self.instance = self._meta.document.to_python(data)

        if commit:
            self.instance.save()

        return self.instance

class DocumentForm(BaseDocumentForm):
    """ The document form object """
    __metaclass__ = DocumentFormMetaClass

class UserMeta(object):
    def __init__(self, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)

def documentform_factory(document, form=DocumentForm, fields=None, exclude=None,
                             formfield_callback=None, schema=None, dotpath=None):
    meta = UserMeta(document=document,
                    fields=fields,
                    exclude=exclude,
                    formfield_callback=formfield_callback,
                    schema=schema,
                    dotpath=dotpath,)
    class FactoryForm(form):
        Meta = meta

    return FactoryForm

<commit_msg>fixed bad if statement<commit_after># -*- coding: utf-8 -*-
#
# Copyright (c) 2008-2009 Benoit Chesneau <benoitc@e-engura.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# code heavily inspired from django.forms.models
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#    1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#    3. Neither the name of Django nor the names of its contributors may be used
#       to endorse or promote products derived from this software without
#       specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

""" Implement DocumentForm object. It map Document objects to Form and
works like ModelForm object :

    >>> from couchdbkit.ext.django.forms  import DocumentForm

    # Create the form class.
    >>> class ArticleForm(DocumentForm):
    ...     class Meta:
    ...         model = Article

    # Creating a form to add an article.
    >>> form = ArticleForm()

    # Creating a form to change an existing article.
    >>> article = Article.get(someid)
    >>> form = ArticleForm(instance=article)

The generated Form class will have a form field for every model field.
Each document property has a corresponding default form field:

* StringProperty   ->  CharField,
* IntegerProperty  ->  IntegerField,
* DecimalProperty  ->  DecimalField,
* BooleanProperty  ->  BooleanField,
* FloatProperty    ->  FloatField,
* DateTimeProperty ->  DateTimeField,
* DateProperty     ->  DateField,
* TimeProperty     ->  TimeField

More fields types will be supported soon.
"""

from django.utils.datastructures import SortedDict
from django.forms.util import ErrorList
from django.forms.forms import BaseForm, get_declared_fields, ValidationError
from django.forms.widgets import media_property

from dockit.schema.exceptions import DotPathNotFound

import inspect

def document_to_dict(document, instance, properties=None, exclude=None, dotpath=None):
    """
    Returns a dict containing the data in ``instance`` suitable for passing as
    a Form's ``initial`` keyword argument.

    ``properties`` is an optional list of properties names. If provided,
    only the named properties will be included in the returned dict.

    ``exclude`` is an optional list of properties names. If provided, the named
    properties will be excluded from the returned dict, even if they are listed
    in the ``properties`` argument.
    """
    # avoid a circular import
    data = {}
    if dotpath:
        try:
            src_data = instance.dot_notation(dotpath)
        except DotPathNotFound:
            src_data = dict()
    else:
        src_data = instance

    if src_data is None:
        src_data = dict()

    for prop_name in src_data.keys():
        if properties and not prop_name in properties:
            continue
        if exclude and prop_name in exclude:
            continue
        data[prop_name] = src_data[prop_name]
    return data

def fields_for_document(document, properties=None, exclude=None, form_field_callback=None, dotpath=None):
    """
    Returns a ``SortedDict`` containing form fields for the given document.

    ``properties`` is an optional list of properties names. If provided,
    only the named properties will be included in the returned properties.

    ``exclude`` is an optional list of properties names. If provided, the named
    properties will be excluded from the returned properties, even if
    they are listed in the ``properties`` argument.
    """
    if dotpath:
        field = document._meta.dot_notation_to_field(dotpath)
        fields = field.schema._meta.fields
    else:
        fields = document._meta.fields

    field_list = []

    values = []
    if properties:
        values = [fields[prop] for prop in properties if \
                                                prop in fields]
    else:
        values = fields.values()
        #values.sort(lambda a, b: cmp(a.creation_counter, b.creation_counter))

    for prop in values:
        if properties and not prop.name in properties:
            continue
        if exclude and prop.name in exclude:
            continue

        field = prop.formfield()

        if field and form_field_callback:
            defaults = prop.formfield_kwargs()
            field = form_field_callback(prop, type(field), **defaults)
        if field:
            field_list.append((prop.name, field))
    return SortedDict(field_list)

class DocumentFormOptions(object):
    def __init__(self, options=None):
        self.document = getattr(options, 'document', None)
        self.schema = getattr(options, 'schema', None)
        self.properties = getattr(options, 'properties', None)
        self.exclude = getattr(options, 'exclude', None)
        self.form_field_callback = getattr(options, 'form_field_callback', None)
        self.dotpath = getattr(options, 'dotpath', None)

        #lookup the appropriate schema if none is given
        if self.document and not self.schema:
            if self.dotpath:
                field = self.document._meta.dot_notation_to_field(self.dotpath)
                if hasattr(field, 'schema'):
                    self.schema = field.schema
            else:
                self.schema = self.document

class DocumentFormMetaClass(type):
    def __new__(cls, name, bases, attrs):
        try:
            parents = [b for b in bases if issubclass(b, DocumentForm)]
        except NameError:
            # We are defining ModelForm itself.
            parents = None

        declared_fields = get_declared_fields(bases, attrs, False)
        new_class = super(DocumentFormMetaClass, cls).__new__(cls, name, bases,
                    attrs)

        if not parents:
            return new_class

        if 'media' not in attrs:
            new_class.media = media_property(new_class)

        opts = new_class._meta = DocumentFormOptions(getattr(new_class,
                                                'Meta', None))
        if opts.schema:
            fields = fields_for_document(opts.schema, opts.properties,
                                         opts.exclude, form_field_callback=opts.form_field_callback,)
            # Override default docuemnt fields with any custom declared ones
            # (plus, include all the other declared fields).
            new_class.serialized_fields = fields.keys()
            fields.update(declared_fields)
        elif opts.document: #TODO this should no longer be necessary
            # If a document is defined, extract form fields from it.
            fields = fields_for_document(opts.document, opts.properties,
                                         opts.exclude, form_field_callback=opts.form_field_callback,
                                         dotpath=opts.dotpath)
            # Override default docuemnt fields with any custom declared ones
            # (plus, include all the other declared fields).
            new_class.serialized_fields = fields.keys()
            fields.update(declared_fields)
        else:
            fields = declared_fields

        new_class.declared_fields = declared_fields
        new_class.base_fields = fields
        return new_class

class BaseDocumentForm(BaseForm):
    """ Base Document Form object """

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
            initial=None, error_class=ErrorList, label_suffix=":",
            empty_permitted=False, instance=None, dotpath=None):
        opts = self._meta
        self.dotpath = dotpath or opts.dotpath

        if instance is None:
            self.instance = opts.document()
            object_data = {}
        else:
            self.instance = instance
            object_data = document_to_dict(self._meta.document, instance, opts.properties,
                                        opts.exclude, dotpath=self.dotpath)
        if initial is not None:
            object_data.update(initial)

        super(BaseDocumentForm, self).__init__(data, files, auto_id, prefix,
                                            object_data, error_class,
                                            label_suffix, empty_permitted)
        #print self.data, self.initial, self.instance._primitive_data, self.instance._python_data

    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                arg_spec = inspect.getargspec(field.clean)
                if ((hasattr(arg_spec, 'args') and len(arg_spec.args) > 2) or #py2.6+
                     len(arg_spec[0]) > 2): #py2.5
                    initial = self.initial.get(name, field.initial)
                    if callable(initial):
                        initial = initial()
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError, e:
                self._errors[name] = self.error_class(e.messages)
                if name in self.cleaned_data:
                    del self.cleaned_data[name]

    def _inner_save(self, dynamic=True):
        opts = self._meta
        cleaned_data = self.cleaned_data.copy()

        if self.dotpath:
            obj = None
            try:
                obj = self.instance.dot_notation(self.dotpath)
            except:
                pass
            if obj is None:
                obj = opts.schema()
        else:
            data = self.instance.to_primitive(self.instance)
            obj = opts.schema.to_python(data)

        for prop_name in self.serialized_fields:
            if prop_name in cleaned_data.keys():
                value = cleaned_data.pop(prop_name)
                obj[prop_name] = value

        if dynamic:
            for attr_name in cleaned_data.iterkeys():
                if opts.exclude and attr_name in opts.exclude:
                    continue
                value = cleaned_data[attr_name]
                if value is not None:
                    obj[attr_name] = value
        return obj

    def save(self, commit=True, dynamic=True):
        """
        Saves this ``form``'s cleaned_data into document instance
        ``self.instance``.

        If commit=True, then the changes to ``instance`` will be saved to the
        database. Returns ``instance``.
        """
        #_inner_save wraps it in the schema while we wrap it in the document
        self.target_object = self._inner_save(dynamic)

        if self.dotpath:
            self.instance.dot_notation_set_value(self.dotpath, self.target_object)
        else:
            self.instance = self.target_object

        data = self.instance.to_primitive(self.instance)
        self.instance = self._meta.document.to_python(data)

        if commit:
            self.instance.save()

        return self.instance

class DocumentForm(BaseDocumentForm):
    """ The document form object """
    __metaclass__ = DocumentFormMetaClass

class UserMeta(object):
    def __init__(self, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)

def documentform_factory(document, form=DocumentForm, fields=None, exclude=None,
                             formfield_callback=None, schema=None, dotpath=None):
    meta = UserMeta(document=document,
                    fields=fields,
                    exclude=exclude,
                    formfield_callback=formfield_callback,
                    schema=schema,
                    dotpath=dotpath,)
    class FactoryForm(form):
        Meta = meta

    return FactoryForm

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""Data access modules for loading system-of-systems model configuration
"""
from abc import ABCMeta, abstractmethod
import os
from smif.data_layer.load import load, dump

class DataInterface(metaclass=ABCMeta):

    @abstractmethod
    def read_sos_model_runs(self):
        raise NotImplementedError()

    @abstractmethod
    def write_sos_model_run(self, sos_model_run):
        raise NotImplementedError()

    @abstractmethod
    def read_sos_models(self):
        raise NotImplementedError()

    @abstractmethod
    def write_sos_model(self, sos_model):
        raise NotImplementedError()

    @abstractmethod
    def read_sector_models(self):
        raise NotImplementedError()

    @abstractmethod
    def read_sector_model(self, sector_model_name):
        raise NotImplementedError()

    @abstractmethod
    def write_sector_model(self, sector_model):
        raise NotImplementedError()

    @abstractmethod
    def read_region_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_interval_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_units(self):
        raise NotImplementedError()

    @abstractmethod
    def write_region_set(self, data):
        raise NotImplementedError()

    @abstractmethod
    def write_interval_set(self, data):
        raise NotImplementedError()

    @abstractmethod
    def write_units(self, data):
        raise NotImplementedError()

    @abstractmethod
    def read_scenario_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario(self, scenario):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    @abstractmethod
    def read_narrative_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative(self, narrative):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()

class DatafileInterface(DataInterface):
    """Read and write interface to YAML / CSV configuration files

    Arguments
    ---------
    base_folder : str
        The path to the configuration and data files
    """
    def __init__(self, base_folder):
        config_folders = {
            'sos_model_runs': 'config',
            'sos_models': 'config',
            'sector_models': 'config',
            'initial_conditions': 'data',
            'intervals': 'data',
            'interventions': 'data',
            'narratives': 'data',
            'regions': 'data',
            'scenarios': 'data'
        }

        self.filepath = {}
        for config_file in config_folders:
            self.filepath[config_file] = os.path.join(base_folder, config_folders[config_file],
                                                      config_file)

    def read_sos_model_runs(self):
        """Read all system-of-system model runs from Yaml files

        Returns
        -------
        list
            A list of sos_model_run dicts
        """
        sos_model_runs = []

        sos_model_run_names = self._read_yaml_filenames_in_dir(self.filepath['sos_model_runs'])
        for sos_model_run_name in sos_model_run_names:
            sos_model_runs.append(self._read_yaml_file(self.filepath['sos_model_runs'],
                                                       sos_model_run_name))

        return sos_model_runs

    def write_sos_model_run(self, sos_model_run):
        """Write system-of-system model run to Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sos_model_run : dict
            A sos_model_run dictionary
        """
        self._write_yaml_file(self.filepath['sos_model_runs'],
                              sos_model_run['name'], sos_model_run)

    def read_sos_models(self):
        """Read all system-of-system models from Yaml files

        Returns
        -------
        list
            A list of sos_models dicts
        """
        sos_models = []

        sos_model_names = self._read_yaml_filenames_in_dir(self.filepath['sos_models'])
        for sos_model_name in sos_model_names:
            sos_models.append(self._read_yaml_file(self.filepath['sos_models'],
                                                   sos_model_name))
        return sos_models

    def write_sos_model(self, sos_model):
        """Write system-of-system model to Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sos_model : dict
            A sos_model dictionary
        """
        self._write_yaml_file(self.filepath['sos_models'], sos_model['name'], sos_model)

    def read_sector_models(self):
        """Read all sector models from Yaml files

        Returns
        -------
        list
            A list of sector_model dicts
        """
        return self._read_yaml_filenames_in_dir(self.filepath['sector_models'])

    def read_sector_model(self, sector_model_name):
        """Read a sector model from a Yaml file

        Raises an exception when the file does not exists

        Arguments
        ---------
        sector_model_name : str
            Name of the sector_model (sector_model['name'])
        """
        return self._read_yaml_file(self.filepath['sector_models'], sector_model_name)

    def write_sector_model(self, sector_model):
        """Write sector model to a Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sector_model : dict
            A sector_model dictionary
        """
        self._write_yaml_file(self.filepath['sector_models'], sector_model['name'],
                              sector_model)

    def read_region_sets(self):
        raise NotImplementedError()

    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    def read_interval_sets(self):
        raise NotImplementedError()

    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    def read_units(self):
        raise NotImplementedError()

    def write_region_set(self, data):
        raise NotImplementedError()

    def write_interval_set(self, data):
        raise NotImplementedError()

    def write_units(self, data):
        raise NotImplementedError()

    def read_scenario_sets(self):
        raise NotImplementedError()

    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    def write_scenario(self, scenario):
        raise NotImplementedError()

    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    def read_narrative_sets(self):
        raise NotImplementedError()

    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    def write_narrative(self, narrative):
        raise NotImplementedError()

    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()

    def _read_yaml_filenames_in_dir(self, path):
        """Returns the name of the Yaml files in a certain directory

        Arguments
        ---------
        path : str
            Path to directory

        Returns
        -------
        list
            The list of Yaml files in `path`
        """
        files = []
        for filename in os.listdir(path):
            if filename.endswith('.yml'):
                files.append(os.path.splitext(filename)[0])
        return files

    def _read_yaml_file(self, path, filename):
        """
        Arguments
        ---------
        path : str
            Path to directory
        name : str
            Name of file

        Returns
        -------
        dict
            The contents of the Yaml file `name` in `path`
        """
        filename = filename + '.yml'
        filepath = os.path.join(path, filename)
        return load(filepath)

    def _write_yaml_file(self, path, filename, contents):
        """Writes a Dict to a Yaml file

        Arguments
        ---------
        path : str
            Path to directory
        name : str
            Name of file
        contents: dict
            Contents to be written to the file
        """
        filename = filename + '.yml'
        filepath = os.path.join(path, filename)
        dump(contents, filepath)

class DatabaseInterface(DataInterface):
    """ Read and write interface to Database
    """
    def __init__(self, config_path):
        raise NotImplementedError()

    def read_sos_model_runs(self):
        raise NotImplementedError()

    def write_sos_model_run(self, sos_model_run):
        raise NotImplementedError()

    def read_sos_models(self):
        raise NotImplementedError()

    def write_sos_model(self, sos_model):
        raise NotImplementedError()

    def read_sector_models(self):
        raise NotImplementedError()

    def read_sector_model(self, sector_model_name):
        raise NotImplementedError()

    def write_sector_model(self, sector_model):
        raise NotImplementedError()

    def read_region_sets(self):
        raise NotImplementedError()

    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    def read_interval_sets(self):
        raise NotImplementedError()

    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    def read_units(self):
        raise NotImplementedError()

    def write_region_set(self, data):
        raise NotImplementedError()

    def write_interval_set(self, data):
        raise NotImplementedError()

    def write_units(self, data):
        raise NotImplementedError()

    def read_scenario_sets(self):
        raise NotImplementedError()

    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    def write_scenario(self, scenario):
        raise NotImplementedError()

    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    def read_narrative_sets(self):
        raise NotImplementedError()

    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    def write_narrative(self, narrative):
        raise NotImplementedError()

    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()
<commit_msg>Generic function for listing files in dir<commit_after># -*- coding: utf-8 -*-
"""Data access modules for loading system-of-systems model configuration
"""
from abc import ABCMeta, abstractmethod
import os
from smif.data_layer.load import load, dump

class DataInterface(metaclass=ABCMeta):

    @abstractmethod
    def read_sos_model_runs(self):
        raise NotImplementedError()

    @abstractmethod
    def write_sos_model_run(self, sos_model_run):
        raise NotImplementedError()

    @abstractmethod
    def read_sos_models(self):
        raise NotImplementedError()

    @abstractmethod
    def write_sos_model(self, sos_model):
        raise NotImplementedError()

    @abstractmethod
    def read_sector_models(self):
        raise NotImplementedError()

    @abstractmethod
    def read_sector_model(self, sector_model_name):
        raise NotImplementedError()

    @abstractmethod
    def write_sector_model(self, sector_model):
        raise NotImplementedError()

    @abstractmethod
    def read_region_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_interval_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_units(self):
        raise NotImplementedError()

    @abstractmethod
    def write_region_set(self, data):
        raise NotImplementedError()

    @abstractmethod
    def write_interval_set(self, data):
        raise NotImplementedError()

    @abstractmethod
    def write_units(self, data):
        raise NotImplementedError()

    @abstractmethod
    def read_scenario_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario(self, scenario):
        raise NotImplementedError()

    @abstractmethod
    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    @abstractmethod
    def read_narrative_sets(self):
        raise NotImplementedError()

    @abstractmethod
    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    @abstractmethod
    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative(self, narrative):
        raise NotImplementedError()

    @abstractmethod
    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()

class DatafileInterface(DataInterface):
    """Read and write interface to YAML / CSV configuration files

    Arguments
    ---------
    base_folder : str
        The path to the configuration and data files
    """
    def __init__(self, base_folder):
        config_folders = {
            'sos_model_runs': 'config',
            'sos_models': 'config',
            'sector_models': 'config',
            'initial_conditions': 'data',
            'intervals': 'data',
            'interventions': 'data',
            'narratives': 'data',
            'regions': 'data',
            'scenarios': 'data'
        }

        self.filepath = {}
        for config_file in config_folders:
            self.filepath[config_file] = os.path.join(base_folder, config_folders[config_file],
                                                      config_file)

    def read_sos_model_runs(self):
        """Read all system-of-system model runs from Yaml files

        Returns
        -------
        list
            A list of sos_model_run dicts
        """
        sos_model_runs = []

        sos_model_run_names = self._read_filenames_in_dir(self.filepath['sos_model_runs'],
                                                          '.yml')
        for sos_model_run_name in sos_model_run_names:
            sos_model_runs.append(self._read_yaml_file(self.filepath['sos_model_runs'],
                                                       sos_model_run_name))

        return sos_model_runs

    def write_sos_model_run(self, sos_model_run):
        """Write system-of-system model run to Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sos_model_run : dict
            A sos_model_run dictionary
        """
        self._write_yaml_file(self.filepath['sos_model_runs'],
                              sos_model_run['name'], sos_model_run)

    def read_sos_models(self):
        """Read all system-of-system models from Yaml files

        Returns
        -------
        list
            A list of sos_models dicts
        """
        sos_models = []

        sos_model_names = self._read_filenames_in_dir(self.filepath['sos_models'], '.yml')
        for sos_model_name in sos_model_names:
            sos_models.append(self._read_yaml_file(self.filepath['sos_models'],
                                                   sos_model_name))
        return sos_models

    def write_sos_model(self, sos_model):
        """Write system-of-system model to Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sos_model : dict
            A sos_model dictionary
        """
        self._write_yaml_file(self.filepath['sos_models'], sos_model['name'], sos_model)

    def read_sector_models(self):
        """Read all sector models from Yaml files

        Returns
        -------
        list
            A list of sector_model dicts
        """
        return self._read_filenames_in_dir(self.filepath['sector_models'], '.yml')

    def read_sector_model(self, sector_model_name):
        """Read a sector model from a Yaml file

        Raises an exception when the file does not exists

        Arguments
        ---------
        sector_model_name : str
            Name of the sector_model (sector_model['name'])
        """
        return self._read_yaml_file(self.filepath['sector_models'], sector_model_name)

    def write_sector_model(self, sector_model):
        """Write sector model to a Yaml file

        Existing configuration will be overwritten without warning

        Arguments
        ---------
        sector_model : dict
            A sector_model dictionary
        """
        self._write_yaml_file(self.filepath['sector_models'], sector_model['name'],
                              sector_model)

    def read_region_sets(self):
        raise NotImplementedError()

    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    def read_interval_sets(self):
        raise NotImplementedError()

    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    def read_units(self):
        raise NotImplementedError()

    def write_region_set(self, data):
        raise NotImplementedError()

    def write_interval_set(self, data):
        raise NotImplementedError()

    def write_units(self, data):
        raise NotImplementedError()

    def read_scenario_sets(self):
        raise NotImplementedError()

    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    def write_scenario(self, scenario):
        raise NotImplementedError()

    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    def read_narrative_sets(self):
        raise NotImplementedError()

    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    def write_narrative(self, narrative):
        raise NotImplementedError()

    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()

    def _read_filenames_in_dir(self, path, extension):
        """Returns the name of the Yaml files in a certain directory

        Arguments
        ---------
        path : str
            Path to directory
        extension : str
            Extension of files (such as: '.yml' or '.csv')

        Returns
        -------
        list
            The list of files in `path` with extension
        """
        files = []
        for filename in os.listdir(path):
            if filename.endswith(extension):
                files.append(os.path.splitext(filename)[0])
        return files

    def _read_yaml_file(self, path, filename):
        """
        Arguments
        ---------
        path : str
            Path to directory
        name : str
            Name of file

        Returns
        -------
        dict
            The contents of the Yaml file `name` in `path`
        """
        filename = filename + '.yml'
        filepath = os.path.join(path, filename)
        return load(filepath)

    def _write_yaml_file(self, path, filename, contents):
        """Writes a Dict to a Yaml file

        Arguments
        ---------
        path : str
            Path to directory
        name : str
            Name of file
        contents: dict
            Contents to be written to the file
        """
        filename = filename + '.yml'
        filepath = os.path.join(path, filename)
        dump(contents, filepath)

class DatabaseInterface(DataInterface):
    """ Read and write interface to Database
    """
    def __init__(self, config_path):
        raise NotImplementedError()

    def read_sos_model_runs(self):
        raise NotImplementedError()

    def write_sos_model_run(self, sos_model_run):
        raise NotImplementedError()

    def read_sos_models(self):
        raise NotImplementedError()

    def write_sos_model(self, sos_model):
        raise NotImplementedError()

    def read_sector_models(self):
        raise NotImplementedError()

    def read_sector_model(self, sector_model_name):
        raise NotImplementedError()

    def write_sector_model(self, sector_model):
        raise NotImplementedError()

    def read_region_sets(self):
        raise NotImplementedError()

    def read_region_set_data(self, region_set_name):
        raise NotImplementedError()

    def read_interval_sets(self):
        raise NotImplementedError()

    def read_interval_set_data(self, interval_set_name):
        raise NotImplementedError()

    def read_units(self):
        raise NotImplementedError()

    def write_region_set(self, data):
        raise NotImplementedError()

    def write_interval_set(self, data):
        raise NotImplementedError()

    def write_units(self, data):
        raise NotImplementedError()

    def read_scenario_sets(self):
        raise NotImplementedError()

    def read_scenarios(self, scenario_set_name):
        raise NotImplementedError()

    def read_scenario_data(self, scenario_name):
        raise NotImplementedError()

    def write_scenario_set(self, scenario_set):
        raise NotImplementedError()

    def write_scenario(self, scenario):
        raise NotImplementedError()

    def write_scenario_data(self, scenario_name, data):
        raise NotImplementedError()

    def read_narrative_sets(self):
        raise NotImplementedError()

    def read_narratives(self, narrative_set_name):
        raise NotImplementedError()

    def read_narrative_data(self, narrative_name):
        raise NotImplementedError()

    def write_narrative_set(self, narrative_set):
        raise NotImplementedError()

    def write_narrative(self, narrative):
        raise NotImplementedError()

    def write_narrative_data(self, narrative_set_name, data):
        raise NotImplementedError()
<|endoftext|>


<commit_before>"""Support for functionality to interact with Android TV/Fire TV devices."""
import os

from adb_shell.auth.keygen import keygen
from androidtv.adb_manager.adb_manager_sync import ADBPythonSync
from androidtv.setup_async import setup

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONF_DEVICE_CLASS,
    CONF_HOST,
    CONF_PORT,
    EVENT_HOMEASSISTANT_STOP,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.storage import STORAGE_DIR
from homeassistant.helpers.typing import ConfigType

from .const import (
    ANDROID_DEV,
    ANDROID_DEV_OPT,
    CONF_ADB_SERVER_IP,
    CONF_ADB_SERVER_PORT,
    CONF_ADBKEY,
    CONF_STATE_DETECTION_RULES,
    DEFAULT_ADB_SERVER_PORT,
    DEVICE_ANDROIDTV,
    DEVICE_FIRETV,
    DOMAIN,
    PROP_ETHMAC,
    PROP_WIFIMAC,
    SIGNAL_CONFIG_ENTITY,
)

PLATFORMS = [Platform.MEDIA_PLAYER]
RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES]

_INVALID_MACS = {"ff:ff:ff:ff:ff:ff"}

def get_androidtv_mac(dev_props):
    """Return formatted mac from device properties."""
    for prop_mac in (PROP_ETHMAC, PROP_WIFIMAC):
        if if_mac := dev_props.get(prop_mac):
            mac = format_mac(if_mac)
            if mac not in _INVALID_MACS:
                return mac
    return None

def _setup_androidtv(hass, config):
    """Generate an ADB key (if needed) and load it."""
    adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey"))
    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if not os.path.isfile(adbkey):
            # Generate ADB key files
            keygen(adbkey)

        # Load the ADB key
        signer = ADBPythonSync.load_adbkey(adbkey)
        adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        signer = None
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

    return adbkey, signer, adb_log

async def async_connect_androidtv(
    hass, config, *, state_detection_rules=None, timeout=30.0
):
    """Connect to Android device."""
    address = f"{config[CONF_HOST]}:{config[CONF_PORT]}"

    adbkey, signer, adb_log = await hass.async_add_executor_job(
        _setup_androidtv, hass, config
    )

    aftv = await setup(
        config[CONF_HOST],
        config[CONF_PORT],
        adbkey,
        config.get(CONF_ADB_SERVER_IP),
        config.get(CONF_ADB_SERVER_PORT, DEFAULT_ADB_SERVER_PORT),
        state_detection_rules,
        config[CONF_DEVICE_CLASS],
        timeout,
        signer,
    )

    if not aftv.available:
        # Determine the name that will be used for the device in the log
        if config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
            device_name = "Android TV device"
        elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
            device_name = "Fire TV device"
        else:
            device_name = "Android TV / Fire TV device"

        error_message = f"Could not connect to {device_name} at {address} {adb_log}"
        return None, error_message

    return aftv, None

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Set up the Android TV integration."""
    return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Android TV platform."""

    state_det_rules = entry.options.get(CONF_STATE_DETECTION_RULES)
    aftv, error_message = await async_connect_androidtv(
        hass, entry.data, state_detection_rules=state_det_rules
    )
    if not aftv:
        raise ConfigEntryNotReady(error_message)

    async def async_close_connection(event):
        """Close Android TV connection on HA Stop."""
        await aftv.adb_close()

    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_connection)
    )
    entry.async_on_unload(entry.add_update_listener(update_listener))

    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
        ANDROID_DEV: aftv,
        ANDROID_DEV_OPT: entry.options.copy(),
    }

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
        aftv = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV]
        await aftv.adb_close()
        hass.data[DOMAIN].pop(entry.entry_id)

    return unload_ok

async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
    """Update when config_entry options update."""
    reload_opt = False
    old_options = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV_OPT]
    for opt_key, opt_val in entry.options.items():
        if opt_key in RELOAD_OPTIONS:
            old_val = old_options.get(opt_key)
            if old_val is None or old_val != opt_val:
                reload_opt = True
                break

    if reload_opt:
        await hass.config_entries.async_reload(entry.entry_id)
        return

    hass.data[DOMAIN][entry.entry_id][ANDROID_DEV_OPT] = entry.options.copy()
    async_dispatcher_send(hass, f"{SIGNAL_CONFIG_ENTITY}_{entry.entry_id}")
<commit_msg>Remove useless async_setup from AndroidTV (#68561)<commit_after>"""Support for functionality to interact with Android TV/Fire TV devices."""
import os

from adb_shell.auth.keygen import keygen
from androidtv.adb_manager.adb_manager_sync import ADBPythonSync
from androidtv.setup_async import setup as async_androidtv_setup

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONF_DEVICE_CLASS,
    CONF_HOST,
    CONF_PORT,
    EVENT_HOMEASSISTANT_STOP,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.storage import STORAGE_DIR

from .const import (
    ANDROID_DEV,
    ANDROID_DEV_OPT,
    CONF_ADB_SERVER_IP,
    CONF_ADB_SERVER_PORT,
    CONF_ADBKEY,
    CONF_STATE_DETECTION_RULES,
    DEFAULT_ADB_SERVER_PORT,
    DEVICE_ANDROIDTV,
    DEVICE_FIRETV,
    DOMAIN,
    PROP_ETHMAC,
    PROP_WIFIMAC,
    SIGNAL_CONFIG_ENTITY,
)

PLATFORMS = [Platform.MEDIA_PLAYER]
RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES]

_INVALID_MACS = {"ff:ff:ff:ff:ff:ff"}

def get_androidtv_mac(dev_props):
    """Return formatted mac from device properties."""
    for prop_mac in (PROP_ETHMAC, PROP_WIFIMAC):
        if if_mac := dev_props.get(prop_mac):
            mac = format_mac(if_mac)
            if mac not in _INVALID_MACS:
                return mac
    return None

def _setup_androidtv(hass, config):
    """Generate an ADB key (if needed) and load it."""
    adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey"))
    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if not os.path.isfile(adbkey):
            # Generate ADB key files
            keygen(adbkey)

        # Load the ADB key
        signer = ADBPythonSync.load_adbkey(adbkey)
        adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        signer = None
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

    return adbkey, signer, adb_log

async def async_connect_androidtv(
    hass, config, *, state_detection_rules=None, timeout=30.0
):
    """Connect to Android device."""
    address = f"{config[CONF_HOST]}:{config[CONF_PORT]}"

    adbkey, signer, adb_log = await hass.async_add_executor_job(
        _setup_androidtv, hass, config
    )

    aftv = await async_androidtv_setup(
        config[CONF_HOST],
        config[CONF_PORT],
        adbkey,
        config.get(CONF_ADB_SERVER_IP),
        config.get(CONF_ADB_SERVER_PORT, DEFAULT_ADB_SERVER_PORT),
        state_detection_rules,
        config[CONF_DEVICE_CLASS],
        timeout,
        signer,
    )

    if not aftv.available:
        # Determine the name that will be used for the device in the log
        if config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
            device_name = "Android TV device"
        elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
            device_name = "Fire TV device"
        else:
            device_name = "Android TV / Fire TV device"

        error_message = f"Could not connect to {device_name} at {address} {adb_log}"
        return None, error_message

    return aftv, None

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Android TV platform."""

    state_det_rules = entry.options.get(CONF_STATE_DETECTION_RULES)
    aftv, error_message = await async_connect_androidtv(
        hass, entry.data, state_detection_rules=state_det_rules
    )
    if not aftv:
        raise ConfigEntryNotReady(error_message)

    async def async_close_connection(event):
        """Close Android TV connection on HA Stop."""
        await aftv.adb_close()

    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_connection)
    )
    entry.async_on_unload(entry.add_update_listener(update_listener))

    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
        ANDROID_DEV: aftv,
        ANDROID_DEV_OPT: entry.options.copy(),
    }

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
        aftv = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV]
        await aftv.adb_close()
        hass.data[DOMAIN].pop(entry.entry_id)

    return unload_ok

async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
    """Update when config_entry options update."""
    reload_opt = False
    old_options = hass.data[DOMAIN][entry.entry_id][ANDROID_DEV_OPT]
    for opt_key, opt_val in entry.options.items():
        if opt_key in RELOAD_OPTIONS:
            old_val = old_options.get(opt_key)
            if old_val is None or old_val != opt_val:
                reload_opt = True
                break

    if reload_opt:
        await hass.config_entries.async_reload(entry.entry_id)
        return

    hass.data[DOMAIN][entry.entry_id][ANDROID_DEV_OPT] = entry.options.copy()
    async_dispatcher_send(hass, f"{SIGNAL_CONFIG_ENTITY}_{entry.entry_id}")
<|endoftext|>


<commit_before># -*- encoding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2013 Smile (<http://www.smile.fr>). All Rights Reserved
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import logging

from openerp import api
from openerp.osv import fields
from openerp.models import BaseModel

_logger = logging.getLogger(__name__)

native_auto_init = BaseModel._auto_init
native_validate_fields = BaseModel._validate_fields
native_import_data = BaseModel.import_data
native_load = BaseModel.load
native_unlink = BaseModel.unlink

def new_auto_init(self, cr, context=None):
    '''Add foreign key with ondelete = 'set null' for stored fields.function of type many2one'''
    res = native_auto_init(self, cr, context)
    for fieldname, field in self._columns.iteritems():
        if isinstance(field, fields.function) and field._type == 'many2one' and field.store:
            self._m2o_fix_foreign_key(cr, self._table, fieldname, self.pool.get(field._obj), 'set null')
    return res

@api.multi
def new_validate_fields(self, fields_to_validate):
    context = self._context
    if not context.get('no_validate'):
        native_validate_fields(self, fields_to_validate)

# Helper function combining _store_get_values and _store_set_values
@api.multi
def _compute_store_set(self):
    """
    Get the list of stored function field to recompute (via _store_get_values)
    and recompute them (via _store_set_values)
    """
    cr, uid, context = self.env.args
    store_get_result = self._store_get_values(self._columns.keys())
    store_get_result.sort()
    done = {}
    for order, model, ids_to_update, fields_to_recompute in store_get_result:
        key = (model, tuple(fields_to_recompute))
        done.setdefault(key, {})
        # avoid to do several times the same computation
        todo = []
        for id_to_update in ids_to_update:
            if id_to_update not in done[key]:
                done[key][id_to_update] = True
                todo.append(id_to_update)
        self.pool[model]._store_set_values(cr, uid, todo, fields_to_recompute, context)

@api.multi
def store_set_values(self, fields_to_recompute=None):
    # Old-style function fields
    function_fields = [f for f in fields_to_recompute if getattr(self._columns[f], 'store', None)]
    self.pool[self._name]._store_set_values(self._cr, self._uid, self._ids, function_fields, self._context)
    # New-style computed fields
    trigger_fields = sum([list(self._fields[f].compute._depends) for f in fields_to_recompute if self._fields[f].compute], [])
    with self.env.has_todo():
        for f, recs in self.env.todo.iteritems():  # Remove old fields to recompute
            self.env.remove_todo(f, recs)
    for f in trigger_fields:
        self.env.add_todo(f, self)  # Add fields to recompute
    self.modified(set(trigger_fields))
    self.recompute()
    return True

def new_load(self, cr, uid, fields, data, context=None):
    context_copy = context and context.copy() or {}
    context_copy['no_store_function'] = True
    context_copy['no_validate'] = True
    context_copy['defer_parent_store_computation'] = True
    res = native_load(self, cr, uid, fields, data, context_copy)
    ids = res['ids']
    if ids:
        recs = self.browse(cr, uid, ids, context)
        recs._compute_store_set()
        recs._validate_fields(fields)
        self._parent_store_compute(cr)
    return res

def new_import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
    context_copy = context and context.copy() or {}
    context_copy['defer_parent_store_computation'] = True
    return native_import_data(self, cr, uid, fields, datas, mode, current_module, noupdate, context_copy, filename)

@api.multi
def new_unlink(self):
    if hasattr(self.pool[self._name], '_cascade_relations'):
        self = self.with_context(active_test=False)
        if 'unlink_in_cascade' not in self._context:
            self = self.with_context(unlink_in_cascade={self._name: list(self._ids)})
        for model, fnames in self.pool[self._name]._cascade_relations.iteritems():
            domain = ['|'] * (len(fnames) - 1) + [(fname, 'in', self._ids) for fname in fnames]
            sub_model_obj = self.env[model]
            sub_models = sub_model_obj.search(domain)
            sub_model_ids = list(set(sub_models._ids) - set(self._context['unlink_in_cascade'].get(model, [])))
            if sub_model_ids:
                self._context['unlink_in_cascade'].setdefault(model, []).extend(sub_model_ids)
                sub_model_obj.browse(sub_model_ids).unlink()
    if not self.exists():
        return True
    return native_unlink(self)

@api.model
@api.returns('self')
def bulk_create(self, vals_list):
    if not vals_list:
        return []
    cr, uid, context = self.env.args
    context_copy = context and context.copy() or {}
    context_copy['no_store_function'] = True
    context_copy['no_validate'] = True
    context_copy['defer_parent_store_computation'] = True
    ids = []
    if not isinstance(vals_list, list):
        vals_list = [vals_list]
    for vals in vals_list:
        ids.append(self.with_context(context_copy).create(vals).id)
    records = self.browse(ids)
    records._compute_store_set()
    records._validate_fields(vals_list[0])
    self._parent_store_compute()
    return records

BaseModel._auto_init = new_auto_init
BaseModel._compute_store_set = _compute_store_set
BaseModel._validate_fields = new_validate_fields
BaseModel.bulk_create = bulk_create
BaseModel.import_data = new_import_data
BaseModel.load = new_load
BaseModel.store_set_values = store_set_values
BaseModel.unlink = new_unlink
<commit_msg>[FIX] Correct BaseModelstore_set_values<commit_after># -*- encoding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2013 Smile (<http://www.smile.fr>). All Rights Reserved
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import logging

from openerp import api
from openerp.osv import fields
from openerp.models import BaseModel

_logger = logging.getLogger(__name__)

native_auto_init = BaseModel._auto_init
native_validate_fields = BaseModel._validate_fields
native_import_data = BaseModel.import_data
native_load = BaseModel.load
native_unlink = BaseModel.unlink

def new_auto_init(self, cr, context=None):
    '''Add foreign key with ondelete = 'set null' for stored fields.function of type many2one'''
    res = native_auto_init(self, cr, context)
    for fieldname, field in self._columns.iteritems():
        if isinstance(field, fields.function) and field._type == 'many2one' and field.store:
            self._m2o_fix_foreign_key(cr, self._table, fieldname, self.pool.get(field._obj), 'set null')
    return res

@api.multi
def new_validate_fields(self, fields_to_validate):
    context = self._context
    if not context.get('no_validate'):
        native_validate_fields(self, fields_to_validate)

# Helper function combining _store_get_values and _store_set_values
@api.multi
def _compute_store_set(self):
    """
    Get the list of stored function field to recompute (via _store_get_values)
    and recompute them (via _store_set_values)
    """
    cr, uid, context = self.env.args
    store_get_result = self._store_get_values(self._columns.keys())
    store_get_result.sort()
    done = {}
    for order, model, ids_to_update, fields_to_recompute in store_get_result:
        key = (model, tuple(fields_to_recompute))
        done.setdefault(key, {})
        # avoid to do several times the same computation
        todo = []
        for id_to_update in ids_to_update:
            if id_to_update not in done[key]:
                done[key][id_to_update] = True
                todo.append(id_to_update)
        self.pool[model]._store_set_values(cr, uid, todo, fields_to_recompute, context)

def new_load(self, cr, uid, fields, data, context=None):
    context_copy = context and context.copy() or {}
    context_copy['no_store_function'] = True
    context_copy['no_validate'] = True
    context_copy['defer_parent_store_computation'] = True
    res = native_load(self, cr, uid, fields, data, context_copy)
    ids = res['ids']
    if ids:
        recs = self.browse(cr, uid, ids, context)
        recs._compute_store_set()
        recs._validate_fields(fields)
        self._parent_store_compute(cr)
    return res

def new_import_data(self, cr, uid, fields, datas, mode='init', current_module='', noupdate=False, context=None, filename=None):
    context_copy = context and context.copy() or {}
    context_copy['defer_parent_store_computation'] = True
    return native_import_data(self, cr, uid, fields, datas, mode, current_module, noupdate, context_copy, filename)

@api.multi
def new_unlink(self):
    if hasattr(self.pool[self._name], '_cascade_relations'):
        self = self.with_context(active_test=False)
        if 'unlink_in_cascade' not in self._context:
            self = self.with_context(unlink_in_cascade={self._name: list(self._ids)})
        for model, fnames in self.pool[self._name]._cascade_relations.iteritems():
            domain = ['|'] * (len(fnames) - 1) + [(fname, 'in', self._ids) for fname in fnames]
            sub_model_obj = self.env[model]
            sub_models = sub_model_obj.search(domain)
            sub_model_ids = list(set(sub_models._ids) - set(self._context['unlink_in_cascade'].get(model, [])))
            if sub_model_ids:
                self._context['unlink_in_cascade'].setdefault(model, []).extend(sub_model_ids)
                sub_model_obj.browse(sub_model_ids).unlink()
    if not self.exists():
        return True
    return native_unlink(self)

@api.model
@api.returns('self')
def bulk_create(self, vals_list):
    if not vals_list:
        return []
    cr, uid, context = self.env.args
    context_copy = context and context.copy() or {}
    context_copy['no_store_function'] = True
    context_copy['no_validate'] = True
    context_copy['defer_parent_store_computation'] = True
    ids = []
    if not isinstance(vals_list, list):
        vals_list = [vals_list]
    for vals in vals_list:
        ids.append(self.with_context(context_copy).create(vals).id)
    records = self.browse(ids)
    records._compute_store_set()
    records._validate_fields(vals_list[0])
    self._parent_store_compute()
    return records

BaseModel._auto_init = new_auto_init
BaseModel._compute_store_set = _compute_store_set
BaseModel._validate_fields = new_validate_fields
BaseModel.bulk_create = bulk_create
BaseModel.import_data = new_import_data
BaseModel.load = new_load
BaseModel.store_set_values = BaseModel._store_set_values
BaseModel.unlink = new_unlink
<|endoftext|>


<commit_before>"""Support for Luftdaten stations."""
from __future__ import annotations

import logging

from luftdaten import Luftdaten
from luftdaten.exceptions import LuftdatenError
import voluptuous as vol

from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
    CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    CONF_MONITORED_CONDITIONS,
    CONF_SCAN_INTERVAL,
    CONF_SENSORS,
    CONF_SHOW_ON_MAP,
    DEVICE_CLASS_HUMIDITY,
    DEVICE_CLASS_PRESSURE,
    DEVICE_CLASS_TEMPERATURE,
    PERCENTAGE,
    PRESSURE_PA,
    TEMP_CELSIUS,
    Platform,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval

from .config_flow import configured_sensors, duplicate_stations
from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN

_LOGGER = logging.getLogger(__name__)

DATA_LUFTDATEN = "luftdaten"
DATA_LUFTDATEN_CLIENT = "data_luftdaten_client"
DATA_LUFTDATEN_LISTENER = "data_luftdaten_listener"
DEFAULT_ATTRIBUTION = "Data provided by luftdaten.info"

PLATFORMS = [Platform.SENSOR]

SENSOR_HUMIDITY = "humidity"
SENSOR_PM10 = "P1"
SENSOR_PM2_5 = "P2"
SENSOR_PRESSURE = "pressure"
SENSOR_PRESSURE_AT_SEALEVEL = "pressure_at_sealevel"
SENSOR_TEMPERATURE = "temperature"

TOPIC_UPDATE = f"{DOMAIN}_data_update"

SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
    SensorEntityDescription(
        key=SENSOR_TEMPERATURE,
        name="Temperature",
        native_unit_of_measurement=TEMP_CELSIUS,
        device_class=DEVICE_CLASS_TEMPERATURE,
    ),
    SensorEntityDescription(
        key=SENSOR_HUMIDITY,
        name="Humidity",
        icon="mdi:water-percent",
        native_unit_of_measurement=PERCENTAGE,
        device_class=DEVICE_CLASS_HUMIDITY,
    ),
    SensorEntityDescription(
        key=SENSOR_PRESSURE,
        name="Pressure",
        icon="mdi:arrow-down-bold",
        native_unit_of_measurement=PRESSURE_PA,
        device_class=DEVICE_CLASS_PRESSURE,
    ),
    SensorEntityDescription(
        key=SENSOR_PRESSURE_AT_SEALEVEL,
        name="Pressure at sealevel",
        icon="mdi:download",
        native_unit_of_measurement=PRESSURE_PA,
        device_class=DEVICE_CLASS_PRESSURE,
    ),
    SensorEntityDescription(
        key=SENSOR_PM10,
        name="PM10",
        icon="mdi:thought-bubble",
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    SensorEntityDescription(
        key=SENSOR_PM2_5,
        name="PM2.5",
        icon="mdi:thought-bubble-outline",
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]

SENSOR_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
            cv.ensure_list, [vol.In(SENSOR_KEYS)]
        )
    }
)

CONFIG_SCHEMA = vol.Schema(
    vol.All(
        cv.deprecated(DOMAIN),
        {
            DOMAIN: vol.Schema(
                {
                    vol.Required(CONF_SENSOR_ID): cv.positive_int,
                    vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
                    vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
                    vol.Optional(
                        CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
                    ): cv.time_period,
                }
            )
        },
    ),
    extra=vol.ALLOW_EXTRA,
)

@callback
def _async_fixup_sensor_id(hass, config_entry, sensor_id):
    hass.config_entries.async_update_entry(
        config_entry, data={**config_entry.data, CONF_SENSOR_ID: int(sensor_id)}
    )

async def async_setup(hass, config):
    """Set up the Luftdaten component."""
    hass.data[DOMAIN] = {}
    hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT] = {}
    hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER] = {}

    if DOMAIN not in config:
        return True

    conf = config[DOMAIN]
    station_id = conf[CONF_SENSOR_ID]

    if station_id not in configured_sensors(hass):
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN,
                context={"source": SOURCE_IMPORT},
                data={
                    CONF_SENSORS: conf[CONF_SENSORS],
                    CONF_SENSOR_ID: conf[CONF_SENSOR_ID],
                    CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP],
                },
            )
        )

    hass.data[DOMAIN][CONF_SCAN_INTERVAL] = conf[CONF_SCAN_INTERVAL]

    return True

async def async_setup_entry(hass, config_entry):
    """Set up Luftdaten as config entry."""

    if not isinstance(config_entry.data[CONF_SENSOR_ID], int):
        _async_fixup_sensor_id(hass, config_entry, config_entry.data[CONF_SENSOR_ID])

    if (
        config_entry.data[CONF_SENSOR_ID] in duplicate_stations(hass)
        and config_entry.source == SOURCE_IMPORT
    ):
        _LOGGER.warning(
            "Removing duplicate sensors for station %s",
            config_entry.data[CONF_SENSOR_ID],
        )
        hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
        return False

    try:
        luftdaten = LuftDatenData(
            Luftdaten(config_entry.data[CONF_SENSOR_ID]),
            config_entry.data.get(CONF_SENSORS, {}).get(
                CONF_MONITORED_CONDITIONS, SENSOR_KEYS
            ),
        )
        await luftdaten.async_update()
        hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT][config_entry.entry_id] = luftdaten
    except LuftdatenError as err:
        raise ConfigEntryNotReady from err

    hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)

    async def refresh_sensors(event_time):
        """Refresh Luftdaten data."""
        await luftdaten.async_update()
        async_dispatcher_send(hass, TOPIC_UPDATE)

    hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER][
        config_entry.entry_id
    ] = async_track_time_interval(
        hass,
        refresh_sensors,
        hass.data[DOMAIN].get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
    )

    return True

async def async_unload_entry(hass, config_entry):
    """Unload an Luftdaten config entry."""
    remove_listener = hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER].pop(
        config_entry.entry_id
    )
    remove_listener()

    hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT].pop(config_entry.entry_id)

    return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

class LuftDatenData:
    """Define a generic Luftdaten object."""

    def __init__(self, client, sensor_conditions):
        """Initialize the Luftdata object."""
        self.client = client
        self.data = {}
        self.sensor_conditions = sensor_conditions

    async def async_update(self):
        """Update sensor/binary sensor data."""
        try:
            await self.client.get_data()

            if self.client.values:
                self.data[DATA_LUFTDATEN] = self.client.values
                self.data[DATA_LUFTDATEN].update(self.client.meta)

        except LuftdatenError:
            _LOGGER.error("Unable to retrieve data from luftdaten.info")
<commit_msg>Use SensorDeviceClass enum in Luftdaten (#61746)<commit_after>"""Support for Luftdaten stations."""
from __future__ import annotations

import logging

from luftdaten import Luftdaten
from luftdaten.exceptions import LuftdatenError
import voluptuous as vol

from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
    CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    CONF_MONITORED_CONDITIONS,
    CONF_SCAN_INTERVAL,
    CONF_SENSORS,
    CONF_SHOW_ON_MAP,
    PERCENTAGE,
    PRESSURE_PA,
    TEMP_CELSIUS,
    Platform,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval

from .config_flow import configured_sensors, duplicate_stations
from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN

_LOGGER = logging.getLogger(__name__)

DATA_LUFTDATEN = "luftdaten"
DATA_LUFTDATEN_CLIENT = "data_luftdaten_client"
DATA_LUFTDATEN_LISTENER = "data_luftdaten_listener"
DEFAULT_ATTRIBUTION = "Data provided by luftdaten.info"

PLATFORMS = [Platform.SENSOR]

SENSOR_HUMIDITY = "humidity"
SENSOR_PM10 = "P1"
SENSOR_PM2_5 = "P2"
SENSOR_PRESSURE = "pressure"
SENSOR_PRESSURE_AT_SEALEVEL = "pressure_at_sealevel"
SENSOR_TEMPERATURE = "temperature"

TOPIC_UPDATE = f"{DOMAIN}_data_update"

SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
    SensorEntityDescription(
        key=SENSOR_TEMPERATURE,
        name="Temperature",
        native_unit_of_measurement=TEMP_CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
    ),
    SensorEntityDescription(
        key=SENSOR_HUMIDITY,
        name="Humidity",
        icon="mdi:water-percent",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
    ),
    SensorEntityDescription(
        key=SENSOR_PRESSURE,
        name="Pressure",
        icon="mdi:arrow-down-bold",
        native_unit_of_measurement=PRESSURE_PA,
        device_class=SensorDeviceClass.PRESSURE,
    ),
    SensorEntityDescription(
        key=SENSOR_PRESSURE_AT_SEALEVEL,
        name="Pressure at sealevel",
        icon="mdi:download",
        native_unit_of_measurement=PRESSURE_PA,
        device_class=SensorDeviceClass.PRESSURE,
    ),
    SensorEntityDescription(
        key=SENSOR_PM10,
        name="PM10",
        icon="mdi:thought-bubble",
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    SensorEntityDescription(
        key=SENSOR_PM2_5,
        name="PM2.5",
        icon="mdi:thought-bubble-outline",
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]

SENSOR_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
            cv.ensure_list, [vol.In(SENSOR_KEYS)]
        )
    }
)

CONFIG_SCHEMA = vol.Schema(
    vol.All(
        cv.deprecated(DOMAIN),
        {
            DOMAIN: vol.Schema(
                {
                    vol.Required(CONF_SENSOR_ID): cv.positive_int,
                    vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
                    vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
                    vol.Optional(
                        CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
                    ): cv.time_period,
                }
            )
        },
    ),
    extra=vol.ALLOW_EXTRA,
)

@callback
def _async_fixup_sensor_id(hass, config_entry, sensor_id):
    hass.config_entries.async_update_entry(
        config_entry, data={**config_entry.data, CONF_SENSOR_ID: int(sensor_id)}
    )

async def async_setup(hass, config):
    """Set up the Luftdaten component."""
    hass.data[DOMAIN] = {}
    hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT] = {}
    hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER] = {}

    if DOMAIN not in config:
        return True

    conf = config[DOMAIN]
    station_id = conf[CONF_SENSOR_ID]

    if station_id not in configured_sensors(hass):
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN,
                context={"source": SOURCE_IMPORT},
                data={
                    CONF_SENSORS: conf[CONF_SENSORS],
                    CONF_SENSOR_ID: conf[CONF_SENSOR_ID],
                    CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP],
                },
            )
        )

    hass.data[DOMAIN][CONF_SCAN_INTERVAL] = conf[CONF_SCAN_INTERVAL]

    return True

async def async_setup_entry(hass, config_entry):
    """Set up Luftdaten as config entry."""

    if not isinstance(config_entry.data[CONF_SENSOR_ID], int):
        _async_fixup_sensor_id(hass, config_entry, config_entry.data[CONF_SENSOR_ID])

    if (
        config_entry.data[CONF_SENSOR_ID] in duplicate_stations(hass)
        and config_entry.source == SOURCE_IMPORT
    ):
        _LOGGER.warning(
            "Removing duplicate sensors for station %s",
            config_entry.data[CONF_SENSOR_ID],
        )
        hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
        return False

    try:
        luftdaten = LuftDatenData(
            Luftdaten(config_entry.data[CONF_SENSOR_ID]),
            config_entry.data.get(CONF_SENSORS, {}).get(
                CONF_MONITORED_CONDITIONS, SENSOR_KEYS
            ),
        )
        await luftdaten.async_update()
        hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT][config_entry.entry_id] = luftdaten
    except LuftdatenError as err:
        raise ConfigEntryNotReady from err

    hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)

    async def refresh_sensors(event_time):
        """Refresh Luftdaten data."""
        await luftdaten.async_update()
        async_dispatcher_send(hass, TOPIC_UPDATE)

    hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER][
        config_entry.entry_id
    ] = async_track_time_interval(
        hass,
        refresh_sensors,
        hass.data[DOMAIN].get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
    )

    return True

async def async_unload_entry(hass, config_entry):
    """Unload an Luftdaten config entry."""
    remove_listener = hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER].pop(
        config_entry.entry_id
    )
    remove_listener()

    hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT].pop(config_entry.entry_id)

    return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

class LuftDatenData:
    """Define a generic Luftdaten object."""

    def __init__(self, client, sensor_conditions):
        """Initialize the Luftdata object."""
        self.client = client
        self.data = {}
        self.sensor_conditions = sensor_conditions

    async def async_update(self):
        """Update sensor/binary sensor data."""
        try:
            await self.client.get_data()

            if self.client.values:
                self.data[DATA_LUFTDATEN] = self.client.values
                self.data[DATA_LUFTDATEN].update(self.client.meta)

        except LuftdatenError:
            _LOGGER.error("Unable to retrieve data from luftdaten.info")
<|endoftext|>