<commit_before>"""
Generic utilities for clustering
"""

import numpy as np

def get_max_value_key(dic):
    """Gets the key for the maximum value in a dict."""
    v = np.array(list(dic.values()))
    k = np.array(list(dic.keys()))

    maxima = np.where(v == np.max(v))[0]
    if len(maxima) == 1:
        return k[maxima[0]]
    else:
        # In order to be consistent, always selects the minimum key
        # (guaranteed to be unique) when there are multiple maximum values.
        return k[maxima[np.argmin(k[maxima])]]

def encode_features(X, enc_map=None):
    """Converts categorical values in each column of X to integers in the range
    [0, n_unique_values_in_column - 1], if X is not already of integer type.

    If mapping is not provided, it is calculated based on the values in X.

    Unknown values during prediction get a value of -1. np.NaNs are ignored
    during encoding, and get treated as unknowns during prediction.
    """
    if enc_map is None:
        fit = True
        # We will calculate enc_map, so initialize the list of column mappings.
        enc_map = []
    else:
        fit = False

    Xenc = np.zeros(X.shape).astype('int')
    for ii in range(X.shape[1]):
        if fit:
            col_enc = {val: jj for jj, val in enumerate(np.unique(X[:, ii]))
                       if not (isinstance(val, float) and np.isnan(val))}
            enc_map.append(col_enc)
        # Unknown categories (including np.NaNs) all get a value of -1.
        Xenc[:, ii] = np.array([enc_map[ii].get(x, -1) for x in X[:, ii]])

    return Xenc, enc_map

def decode_centroids(encoded, mapping):
    """Decodes the encoded centroids array back to the original data
    labels using a list of mappings.
    """
    decoded = []
    for ii in range(encoded.shape[1]):
        # Invert the mapping so that we can decode.
        inv_mapping = {v: k for k, v in mapping[ii].items()}
        decoded.append(np.vectorize(inv_mapping.__getitem__)(encoded[:, ii]))
    return np.atleast_2d(np.array(decoded)).T

def get_unique_rows(a):
    """Gets the unique rows in a numpy array."""
    return np.vstack({tuple(row) for row in a})
<commit_msg>110: anticipate future numpy deprecation<commit_after>"""
Generic utilities for clustering
"""

import numpy as np

def get_max_value_key(dic):
    """Gets the key for the maximum value in a dict."""
    v = np.array(list(dic.values()))
    k = np.array(list(dic.keys()))

    maxima = np.where(v == np.max(v))[0]
    if len(maxima) == 1:
        return k[maxima[0]]
    else:
        # In order to be consistent, always selects the minimum key
        # (guaranteed to be unique) when there are multiple maximum values.
        return k[maxima[np.argmin(k[maxima])]]

def encode_features(X, enc_map=None):
    """Converts categorical values in each column of X to integers in the range
    [0, n_unique_values_in_column - 1], if X is not already of integer type.

    If mapping is not provided, it is calculated based on the values in X.

    Unknown values during prediction get a value of -1. np.NaNs are ignored
    during encoding, and get treated as unknowns during prediction.
    """
    if enc_map is None:
        fit = True
        # We will calculate enc_map, so initialize the list of column mappings.
        enc_map = []
    else:
        fit = False

    Xenc = np.zeros(X.shape).astype('int')
    for ii in range(X.shape[1]):
        if fit:
            col_enc = {val: jj for jj, val in enumerate(np.unique(X[:, ii]))
                       if not (isinstance(val, float) and np.isnan(val))}
            enc_map.append(col_enc)
        # Unknown categories (including np.NaNs) all get a value of -1.
        Xenc[:, ii] = np.array([enc_map[ii].get(x, -1) for x in X[:, ii]])

    return Xenc, enc_map

def decode_centroids(encoded, mapping):
    """Decodes the encoded centroids array back to the original data
    labels using a list of mappings.
    """
    decoded = []
    for ii in range(encoded.shape[1]):
        # Invert the mapping so that we can decode.
        inv_mapping = {v: k for k, v in mapping[ii].items()}
        decoded.append(np.vectorize(inv_mapping.__getitem__)(encoded[:, ii]))
    return np.atleast_2d(np.array(decoded)).T

def get_unique_rows(a):
    """Gets the unique rows in a numpy array."""
    return np.vstack(list({tuple(row) for row in a}))
<|endoftext|>


<commit_before># coding: utf8

#### yarko ---< F/A (financial aid) forms >----
if ENABLE_FINANCIAL_AID:
    db.define_table( 'fa',
       # Idendtification:
       # - Legal name => fa.person.first_name + fa.person.last_name
       # - Address => fa.person.{address1,address2,city,state,country,zip_code}
       # - email address => fa.person.email
       # Registration:
       # - registration type => fa.percon.attendee_type
       db.Field( 'person', db.auth_user, default=auth.user_id, readable=False, writable=False),
       db.Field('created_on','datetime',default=now, readable=False, writable=False),
       db.Field('modified_on','datetime',default=now, readable=False, writable=False),
       db.Field( 'registration_amount', 'double', default='0.00'),
       # Hotel Cost:
       # - number of nights of assitance requested;
       db.Field( 'hotel_nights', 'integer', default=0 ),
       # - total amount requested; label:  "Max 50% of room rate at Crowne Plaza x # nights;" labeled; validated if easy to update room rates.
       db.Field( 'total_lodging_amount', 'double', default='0.00'),
       db.Field( 'roommates', 'string', length=128, default=''),
       # Transportation:
       # - method of transportation / details;
       # db.Field( 'method_of_transportation', 'string', default=''),
       db.Field( 'transportation_details', 'text', default=''),
       # - total amount requested; label: "If you want assistance with your transportation costs, please provide a rough estimate (to nearest US$100)
       #       of how much a round-trip will cost.  Please update your request once final cost is known."
       db.Field( 'transportation_amount', 'double', default='0.00', ),
       # Total:  - read-only field calculated from above 3 sections
       # - registration dollar amount requested; (let applicant specify, as they can ask for just a portion)
       db.Field( 'total_amount_requested', 'double', default='0.0'), # default = ATTENDEE_TYPE_COST[person.attendee_type]),
       #
       # Additional fileds:
       # - minimum at. requested; label "In addition to the desired amount, state the minimum amount of aid you require, below
       #  which you will not be able to attend PyCon.  If we are unable to allocate this minumum amount, we will decline your application
       #  and allocate the funds to others."
       db.Field( 'minimum_amount_requested', 'double', default='0.00', ),
       # - Rational " State why you should come to PyCon, and what you will be doing.
       #    We don't need an essay, but please provide a few sentences of explanation.
       #   Priority will be given to people who make significant contributions to PyCon
       #   and the Python community (e.g. students working on a task, conference speakers,
       #   sprint leaders, developers critical to a sprint, super-enthusiastic sprint newbies
       #   who will give 110% for their project, or people doing public service work with Python)."
       db.Field( 'rationale', 'text', default='' ),
       migrate=migrate)

    db.fa.person.requires=IS_IN_DB(db,'auth_user.id','%(last_name)s, %(first_name)s [%(id)s]')

    db.fa.registration_amount.comment= T('(in ARS pesos)')
    db.fa.total_lodging_amount.comment= T('(in ARS pesos)')
    ##db.fa.roommates.comment= XML(str(T('(%s)',A('instructions',_href='#roommates'))))
    db.fa.transportation_details.comment = T('(dates, airports codes, bus stations, etc.)')
    db.fa.transportation_amount.comment= T('(in ARS pesos)')
    db.fa.total_amount_requested.comment= T('(in ARS pesos)')
    db.fa.minimum_amount_requested.comment= T('(in ARS pesos)')
    db.fa.rationale.comment= T('describe why you want to come to PyCon')
    #### ---< END: F/A forms >---

    #### end fixup

    body_template="""
    Your Financial Aid Application has been %s.
    Thank you; your updates will be reviewed.

    Time:     %s
      Name:        %s %s [#%s]
      Address:     %s
                   %s, %s %s (%s)
      Email:       %s

    APPLICATION:
      Registration Type:          %s
      Registration Amount:        $ %4.2f

      Number Nights:              %d
      Total Lodging Amount:       $ %4.2f
      Roommates:                  %s

    TRANSPORTATION:
    %s

      Transportation Amount:     $ %4.2f

    TOTAL REQUEST:           $ %5.2f
    Minimum Reguest:         $ %5.2f

    RATIONALE:
    %s

    (This email was automatically generated by PyConAr2012 Registration System)
    """

    def email_fa(mode):
        fa=db(db.fa.person==auth.user.id).select()[0]
        person=db(db.auth_user.id==auth.user.id).select()[0]
        body=body_template % (mode, fa.modified_on,
                              person.first_name,person.last_name,person.id,
                              person.address,person.city,person.state,person.zip_code,person.country,
                              person.email,
                              person.attendee_type,
                              fa.registration_amount, fa.hotel_nights, fa.total_lodging_amount, fa.roommates,
                              fa.transportation_details, fa.transportation_amount,
                              fa.total_amount_requested, fa.minimum_amount_requested,
                              fa.rationale
                              )
        if False:  # careful!  I've only just printed this to console, so only use for local testing
            # you could add this:
            # if request.env.http_host == '127.0.0.1:8000'
            # or... output to the screen....
            # response.headers['Content-Type']='text/plain'
            # return str("t2.email(\n"+EMAIL_SENDER+"["+FA_EMAIL_TO+person.email+"]"+"\nsubject="+'PyCon FA Application Updated [#%s]' % fa.id+body+"\n)")
            print "t2.email(\n",EMAIL_SENDER,[FA_EMAIL_TO,person.email],"\nsubject=",'PyCon FA Application Updated [#%s]' % fa.id,body,")"
        else:
            mail.send(to=[FA_EMAIL_TO,person.email],
                      subject='Financial Aid Application Updated [#%s]' % fa.id,
                      message=body)

    def email_fa_select(query=db.fa.id>0):
        for fa in db(query).select():
            person=db(db.auth_user.id==fa.person).select()[0]
            body=body_template % ("retrieved by a manager", fa.modified_on,
                                  person.first_name,person.last_name,person.id,
                                  person.address1,person.address2,person.city,person.state,person.zip_code,person.country,
                                  person.email,
                                  person.attendee_type,
                                  fa.registration_amount, fa.hotel_nights, fa.total_lodging_amount, fa.roommates,
                                  fa.transportation_details, fa.transportation_amount,
                                  fa.total_amount_requested, fa.minimum_amount_requested,
                                  fa.rationale
                                  )
            if False:  # careful!  I've only just printed this to console, so only use for local testing
                # you could add this:
                # if request.env.http_host == '127.0.0.1:8000'
                # or... output to the screen....
                # response.headers['Content-Type']='text/plain'
                # return str("t2.email(\n"+EMAIL_SENDER+"["+FA_EMAIL_TO+"]"+"\nsubject="+'PyCon FA Application Updated [#%s]' % fa.id+ body+ "\n)")
                print "t2.email(\n",EMAIL_SENDER,[FA_EMAIL_TO],"\nsubject=",'PyCon FA Application Updated [#%s]' % fa.id, body, ")"
            else:
                raise "Saraza"
                mail.send(to=FA_EMAIL_TO,subject='PyCon FA Application Updated [#%s]' % fa.id, message=body)
<commit_msg>minor fixes to FA<commit_after># coding: utf8

#### yarko ---< F/A (financial aid) forms >----
if ENABLE_FINANCIAL_AID:
    db.define_table( 'fa',
       # Idendtification:
       # - Legal name => fa.person.first_name + fa.person.last_name
       # - Address => fa.person.{address1,address2,city,state,country,zip_code}
       # - email address => fa.person.email
       # Registration:
       # - registration type => fa.percon.attendee_type
       db.Field( 'person', db.auth_user, default=auth.user_id, readable=False, writable=False),
       db.Field('created_on','datetime',default=now, readable=False, writable=False),
       db.Field('modified_on','datetime',default=now, readable=False, writable=False),
       db.Field( 'registration_amount', 'boolean', default=False),
       # Hotel Cost:
       # - number of nights of assitance requested;
       db.Field( 'hotel_nights', 'integer', default=0 ),
       # - total amount requested; label:  "Max 50% of room rate at Crowne Plaza x # nights;" labeled; validated if easy to update room rates.
       db.Field( 'total_lodging_amount', 'double', default='0.00'),
       db.Field( 'roommates', 'string', length=128, default=''),
       # Transportation:
       # - method of transportation / details;
       # db.Field( 'method_of_transportation', 'string', default=''),
       db.Field( 'transportation_details', 'text', default=''),
       # - total amount requested; label: "If you want assistance with your transportation costs, please provide a rough estimate (to nearest US$100)
       #       of how much a round-trip will cost.  Please update your request once final cost is known."
       db.Field( 'transportation_amount', 'double', default='0.00', ),
       # Total:  - read-only field calculated from above 3 sections
       # - registration dollar amount requested; (let applicant specify, as they can ask for just a portion)
       db.Field( 'total_amount_requested', 'double', default='0.0'), # default = ATTENDEE_TYPE_COST[person.attendee_type]),
       #
       # Additional fileds:
       # - minimum at. requested; label "In addition to the desired amount, state the minimum amount of aid you require, below
       #  which you will not be able to attend PyCon.  If we are unable to allocate this minumum amount, we will decline your application
       #  and allocate the funds to others."
       db.Field( 'minimum_amount_requested', 'double', default='0.00', ),
       # - Rational " State why you should come to PyCon, and what you will be doing.
       #    We don't need an essay, but please provide a few sentences of explanation.
       #   Priority will be given to people who make significant contributions to PyCon
       #   and the Python community (e.g. students working on a task, conference speakers,
       #   sprint leaders, developers critical to a sprint, super-enthusiastic sprint newbies
       #   who will give 110% for their project, or people doing public service work with Python)."
       db.Field( 'rationale', 'text', default='' ),
       migrate=migrate)

    db.fa.person.requires=IS_IN_DB(db,'auth_user.id','%(last_name)s, %(first_name)s [%(id)s]')

    db.fa.registration_amount.comment= T('(cost TBD)')
    db.fa.total_lodging_amount.comment= T('(in ARS pesos)')
    ##db.fa.roommates.comment= XML(str(T('(%s)',A('instructions',_href='#roommates'))))
    db.fa.transportation_details.comment = T('(dates, airports codes, bus stations, etc.)')
    db.fa.transportation_amount.comment= T('(in ARS pesos)')
    db.fa.total_amount_requested.comment= T('(in ARS pesos)')
    db.fa.minimum_amount_requested.comment= T('(in ARS pesos)')
    db.fa.rationale.comment= T('describe why you want to come to PyCon')
    #### ---< END: F/A forms >---

    #### end fixup

    body_template="""
    Your Financial Aid Application has been %s.
    Thank you; your updates will be reviewed.

    Time:     %s
      Name:        %s %s [#%s]
      Address:     %s
                   %s, %s %s (%s)
      Email:       %s

    APPLICATION:
      Registration Type:          %s
      Registration Amount:        $ %4.2f

      Number Nights:              %d
      Total Lodging Amount:       $ %4.2f
      Roommates:                  %s

    TRANSPORTATION:
    %s

      Transportation Amount:     $ %4.2f

    TOTAL REQUEST:           $ %5.2f
    Minimum Reguest:         $ %5.2f

    RATIONALE:
    %s

    (This email was automatically generated by PyConAr2012 Registration System)
    """

    def email_fa(mode):
        fa=db(db.fa.person==auth.user.id).select()[0]
        person=db(db.auth_user.id==auth.user.id).select()[0]
        body=body_template % (mode, fa.modified_on,
                              person.first_name,person.last_name,person.id,
                              person.address,person.city,person.state,person.zip_code,person.country,
                              person.email,
                              person.attendee_type,
                              fa.registration_amount, fa.hotel_nights, fa.total_lodging_amount, fa.roommates,
                              fa.transportation_details, fa.transportation_amount,
                              fa.total_amount_requested, fa.minimum_amount_requested,
                              fa.rationale
                              )
        if False:  # careful!  I've only just printed this to console, so only use for local testing
            # you could add this:
            # if request.env.http_host == '127.0.0.1:8000'
            # or... output to the screen....
            # response.headers['Content-Type']='text/plain'
            # return str("t2.email(\n"+EMAIL_SENDER+"["+FA_EMAIL_TO+person.email+"]"+"\nsubject="+'PyCon FA Application Updated [#%s]' % fa.id+body+"\n)")
            print "t2.email(\n",EMAIL_SENDER,[FA_EMAIL_TO,person.email],"\nsubject=",'PyCon FA Application Updated [#%s]' % fa.id,body,")"
        else:
            mail.send(to=[FA_EMAIL_TO,person.email],
                      subject='Financial Aid Application Updated [#%s]' % fa.id,
                      message=body)

    def email_fa_select(query=db.fa.id>0):
        for fa in db(query).select():
            person=db(db.auth_user.id==fa.person).select()[0]
            body=body_template % ("retrieved by a manager", fa.modified_on,
                                  person.first_name,person.last_name,person.id,
                                  person.address1,person.address2,person.city,person.state,person.zip_code,person.country,
                                  person.email,
                                  person.attendee_type,
                                  fa.registration_amount, fa.hotel_nights, fa.total_lodging_amount, fa.roommates,
                                  fa.transportation_details, fa.transportation_amount,
                                  fa.total_amount_requested, fa.minimum_amount_requested,
                                  fa.rationale
                                  )
            if False:  # careful!  I've only just printed this to console, so only use for local testing
                # you could add this:
                # if request.env.http_host == '127.0.0.1:8000'
                # or... output to the screen....
                # response.headers['Content-Type']='text/plain'
                # return str("t2.email(\n"+EMAIL_SENDER+"["+FA_EMAIL_TO+"]"+"\nsubject="+'PyCon FA Application Updated [#%s]' % fa.id+ body+ "\n)")
                print "t2.email(\n",EMAIL_SENDER,[FA_EMAIL_TO],"\nsubject=",'PyCon FA Application Updated [#%s]' % fa.id, body, ")"
            else:
                raise "Saraza"
                mail.send(to=FA_EMAIL_TO,subject='PyCon FA Application Updated [#%s]' % fa.id, message=body)
<|endoftext|>


<commit_before>r"""
Kontsevich graph sums

"""
from sage.kontsevich_graph_series.kontsevich_graph import KontsevichGraph
from sage.structure.element import ModuleElement
from sage.modules.module import Module
from sage.symbolic.ring import is_SymbolicExpressionRing

class KontsevichGraphSum(ModuleElement):
    def __init__(self, parent, terms):
        """
        Kontsevich graph sum.

        INPUT:

        - ``parent`` -- a ``KontsevichGraphSums`` module.
        - ``terms`` -- list of ``(coefficient, graph)`` tuples,
          where ``coefficient`` is in ``parent.base_ring()``,
          and ``graph`` is an immutable KontsevichGraph.

        OUTPUT:

        A formal sum of Kontsevich graphs.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)])
            1/2*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        if terms == 0:
            terms = []
        if not isinstance(terms, list):
            raise TypeError('Input must be a list of terms.')
        if not all(isinstance(t, tuple) and len(t) == 2 for t in terms):
            raise TypeError('Terms must be (coefficient, graph) tuples.')
        if not all(c in parent.base_ring() and isinstance(g, KontsevichGraph)
                   and getattr(g, '_immutable', False) for (c,g) in terms):
            raise TypeError('Coefficients must be in base ring, and ' +
                            'graphs must be immutable KontsevichGraphs.')
        self._terms = terms
        ModuleElement.__init__(self, parent=parent)
    def _rmul_(self, c):
        """
        Return the product of ``c`` and ``self``.

        INPUT:

        - ``c`` -- multiplicand.

        OUTPUT:

        Kontsevich graph sum with coefficients ``c`` times those of ``self``.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: 2*KontsevichGraphSum(K, [(1/2, KG)])
            1*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        return self.parent()([(c*d,g) for (d,g) in self._terms])
    def _add_(self, other):
        """
        Return the sum of ``self`` and ``other``.

        INPUT:

        - ``other`` -- a KontsevichGraphSum.

        OUTPUT:

        The sum of ``self`` and ``other``.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)]) + \
                  KontsevichGraphSum(K, [(1/2, KG)])
            1*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        return self.parent()(self._terms + other._terms)
    def __cmp__(self, other):
        """
        Compare ``self`` and ``other`` for equality.

        Currently tests for exact equality of the lists of terms.

        INPUT:

        - ``other`` -- a KontsevichGraphSum with the same parent.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: A = KontsevichGraphSum(K, [(1/2, KG)]) + \
                  KontsevichGraphSum(K, [(1/2, KG)])
            sage: B = KontsevichGraphSum(K, [(1, KG)])
            sage: A == B
            False
        """
        return cmp(self._terms, other._terms)
    def __hash__(self):
        """
        Return the hash value.
        """
        return hash(tuple(self._terms))
    def reduce(self):
        """
        Reduce the sum by collecting terms with the same graph.
        """
        graphs = set(g for (c,g) in self._terms)
        coefficient = lambda g: sum(c for (c,h) in self._terms if h == g)
        self._terms = [(coefficient(g), g) for g in graphs]
    def _repr_(self):
        """
        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)])
            1/2*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        self.reduce()
        if self._terms == []:
            return '0'
        parenthesize = lambda c: str(c)
        if is_SymbolicExpressionRing(self.base_ring()):
            from sage.symbolic.operators import add_vararg
            is_sum = lambda x: self.base_ring()(x).operator() == add_vararg
            parenthesize = lambda c: '(%s)' % c if is_sum(c) else c
        return ' + '.join('%s*(%s)' % (parenthesize(c), g)
                          for (c,g) in self._terms)

class KontsevichGraphSums(Module):
    """
    The module of Kontsevich graph sums with coefficients in some ring.
    """
    Element = KontsevichGraphSum
    def _repr_(self):
        """
        EXAMPLES::

            sage: KontsevichGraphSums(QQ)
            Module of Kontsevich graph sums over Rational Field
            sage: KontsevichGraphSums(SR)
            Module of Kontsevich graph sums over Symbolic Ring
        """
        return 'Module of Kontsevich graph sums over %s' % self.base_ring()
    def _element_constructor_(self, terms):
        """
        Make a KontsevichGraphSum in ``self`` from ``terms``.

        INPUT:

        - ``terms`` -- KontsevichGraphSum, list of terms, or 0.
        """
        if isinstance(terms, KontsevichGraphSum): terms = terms._terms
        return self.element_class(self, terms)
    def __cmp__(self, other):
        """
        Compare ``self`` and ``other`` for equality.
        """
        if not isinstance(other, KontsevichGraphSums):
            return cmp(type(other), KontsevichGraphSums)
        return cmp(self.base_ring(), other.base_ring())
    def _an_element_(self):
        """
        EXAMPLES::

            sage: KontsevichGraphSums(QQ).an_element()
            1/2*(Kontsevich graph with 1 vertices on 2 ground vertices)
            sage: KontsevichGraphSums(SR).an_element()
            some_variable*(Kontsevich graph with 1 vertices on 2
            ground vertices)

        """
        KG = KontsevichGraph({'F' : {}, 'G' : {}, 1 : {'F', 'G'}},
                             ground_vertices=('F', 'G'),
                             immutable=True)
        return self.element_class(self, [(self.base_ring().an_element(), KG)])
<commit_msg>Change KontsevichGraphSum equality to be a reasonable thing: equality of coefficients of equal graphs.<commit_after>r"""
Kontsevich graph sums

"""
from sage.kontsevich_graph_series.kontsevich_graph import KontsevichGraph
from sage.structure.element import ModuleElement
from sage.modules.module import Module
from sage.symbolic.ring import is_SymbolicExpressionRing

class KontsevichGraphSum(ModuleElement):
    def __init__(self, parent, terms):
        """
        Kontsevich graph sum.

        INPUT:

        - ``parent`` -- a ``KontsevichGraphSums`` module.
        - ``terms`` -- list of ``(coefficient, graph)`` tuples,
          where ``coefficient`` is in ``parent.base_ring()``,
          and ``graph`` is an immutable KontsevichGraph.

        OUTPUT:

        A formal sum of Kontsevich graphs.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)])
            1/2*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        if terms == 0:
            terms = []
        if not isinstance(terms, list):
            raise TypeError('Input must be a list of terms.')
        if not all(isinstance(t, tuple) and len(t) == 2 for t in terms):
            raise TypeError('Terms must be (coefficient, graph) tuples.')
        if not all(c in parent.base_ring() and isinstance(g, KontsevichGraph)
                   and getattr(g, '_immutable', False) for (c,g) in terms):
            raise TypeError('Coefficients must be in base ring, and ' +
                            'graphs must be immutable KontsevichGraphs.')
        self._terms = terms
        ModuleElement.__init__(self, parent=parent)
    def _rmul_(self, c):
        """
        Return the product of ``c`` and ``self``.

        INPUT:

        - ``c`` -- multiplicand.

        OUTPUT:

        Kontsevich graph sum with coefficients ``c`` times those of ``self``.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: 2*KontsevichGraphSum(K, [(1/2, KG)])
            1*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        return self.parent()([(c*d,g) for (d,g) in self._terms])
    def _add_(self, other):
        """
        Return the sum of ``self`` and ``other``.

        INPUT:

        - ``other`` -- a KontsevichGraphSum.

        OUTPUT:

        The sum of ``self`` and ``other``.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)]) + \
                  KontsevichGraphSum(K, [(1/2, KG)])
            1*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        return self.parent()(self._terms + other._terms)
    def __eq__(self, other):
        """
        Compare ``self`` and ``other`` for equality.

        Currently tests for equality of coefficients of identical graphs.

        INPUT:

        - ``other`` -- a KontsevichGraphSum with the same parent.

        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: A = KontsevichGraphSum(K, [(1/2, KG)]) + \
                  KontsevichGraphSum(K, [(1/2, KG)])
            sage: B = KontsevichGraphSum(K, [(1, KG)])
            sage: A == B
            True
        """
        self.reduce()
        other.reduce()
        return set(self._terms) == set(other._terms)
    def __hash__(self):
        """
        Return the hash value.
        """
        return hash(tuple(self._terms))
    def reduce(self):
        """
        Reduce the sum by collecting terms with the same graph.
        """
        graphs = set(g for (c,g) in self._terms)
        coefficient = lambda g: sum(c for (c,h) in self._terms if h == g)
        self._terms = [(coefficient(g), g) for g in graphs]
    def _repr_(self):
        """
        EXAMPLES::

            sage: K = KontsevichGraphSums(QQ)
            sage: KG = KontsevichGraph(ground_vertices=(), immutable=True)
            sage: KontsevichGraphSum(K, [(1/2, KG)])
            1/2*(Kontsevich graph with 0 vertices on 0 ground vertices)
        """
        self.reduce()
        if self._terms == []:
            return '0'
        parenthesize = lambda c: str(c)
        if is_SymbolicExpressionRing(self.base_ring()):
            from sage.symbolic.operators import add_vararg
            is_sum = lambda x: self.base_ring()(x).operator() == add_vararg
            parenthesize = lambda c: '(%s)' % c if is_sum(c) else c
        return ' + '.join('%s*(%s)' % (parenthesize(c), g)
                          for (c,g) in self._terms)

class KontsevichGraphSums(Module):
    """
    The module of Kontsevich graph sums with coefficients in some ring.
    """
    Element = KontsevichGraphSum
    def _repr_(self):
        """
        EXAMPLES::

            sage: KontsevichGraphSums(QQ)
            Module of Kontsevich graph sums over Rational Field
            sage: KontsevichGraphSums(SR)
            Module of Kontsevich graph sums over Symbolic Ring
        """
        return 'Module of Kontsevich graph sums over %s' % self.base_ring()
    def _element_constructor_(self, terms):
        """
        Make a KontsevichGraphSum in ``self`` from ``terms``.

        INPUT:

        - ``terms`` -- KontsevichGraphSum, list of terms, or 0.
        """
        if isinstance(terms, KontsevichGraphSum): terms = terms._terms
        return self.element_class(self, terms)
    def __cmp__(self, other):
        """
        Compare ``self`` and ``other`` for equality.
        """
        if not isinstance(other, KontsevichGraphSums):
            return cmp(type(other), KontsevichGraphSums)
        return cmp(self.base_ring(), other.base_ring())
    def _an_element_(self):
        """
        EXAMPLES::

            sage: KontsevichGraphSums(QQ).an_element()
            1/2*(Kontsevich graph with 1 vertices on 2 ground vertices)
            sage: KontsevichGraphSums(SR).an_element()
            some_variable*(Kontsevich graph with 1 vertices on 2
            ground vertices)

        """
        KG = KontsevichGraph({'F' : {}, 'G' : {}, 1 : {'F', 'G'}},
                             ground_vertices=('F', 'G'),
                             immutable=True)
        return self.element_class(self, [(self.base_ring().an_element(), KG)])
<|endoftext|>


<commit_before>import contextlib
import urllib
from urlparse import urljoin

import django.middleware.gzip
from django.conf import settings
from django.core import urlresolvers
from django.http import (
    HttpResponseRedirect,
    HttpResponseForbidden,
    HttpResponsePermanentRedirect,
)
from django.utils import translation
from django.utils.encoding import iri_to_uri, smart_str
from django.contrib.sessions.middleware import SessionMiddleware
from whitenoise.middleware import WhiteNoiseMiddleware

from .urlresolvers import Prefixer, set_url_prefixer, split_path
from .utils import urlparams, is_untrusted
from .views import handler403

class LocaleURLMiddleware(object):
    """
    Based on zamboni.amo.middleware.
    Tried to use localeurl but it choked on 'en-US' with capital letters.

    1. Search for the locale.
    2. Save it in the request.
    3. Strip them from the URL.
    """

    def process_request(self, request):
        prefixer = Prefixer(request)
        set_url_prefixer(prefixer)
        full_path = prefixer.fix(prefixer.shortened_path)
        lang = request.GET.get('lang')

        if lang in dict(settings.LANGUAGES):
            # Blank out the locale so that we can set a new one. Remove lang
            # from the query params so we don't have an infinite loop.
            prefixer.locale = ''
            new_path = prefixer.fix(prefixer.shortened_path)
            query = dict((smart_str(k), v) for
                         k, v in request.GET.iteritems() if k != 'lang')

            # Never use HttpResponsePermanentRedirect here.
            # Its a temporary redirect and should return with http 302, not 301
            return HttpResponseRedirect(urlparams(new_path, **query))

        if full_path != request.path:
            query_string = request.META.get('QUERY_STRING', '')
            full_path = urllib.quote(full_path.encode('utf-8'))

            if query_string:
                full_path = '%s?%s' % (full_path, query_string)

            response = HttpResponseRedirect(full_path)

            # Vary on Accept-Language if we changed the locale
            old_locale = prefixer.locale
            new_locale, _ = split_path(full_path)
            if old_locale != new_locale:
                response['Vary'] = 'Accept-Language'

            return response

        request.path_info = '/' + prefixer.shortened_path
        request.LANGUAGE_CODE = prefixer.locale or settings.LANGUAGE_CODE
        # prefixer.locale can be '', but we need a real locale code to activate
        # otherwise the request uses the previously handled request's
        # translations.
        translation.activate(prefixer.locale or settings.LANGUAGE_CODE)

    def process_response(self, request, response):
        """Unset the thread-local var we set during `process_request`."""
        # This makes mistaken tests (that should use LocalizingClient but
        # use Client instead) fail loudly and reliably. Otherwise, the set
        # prefixer bleeds from one test to the next, making tests
        # order-dependent and causing hard-to-track failures.
        set_url_prefixer(None)
        return response

    def process_exception(self, request, exception):
        set_url_prefixer(None)

class Forbidden403Middleware(object):
    """
    Renders a 403.html page if response.status_code == 403.
    """
    def process_response(self, request, response):
        if isinstance(response, HttpResponseForbidden):
            return handler403(request)
        # If not 403, return response unmodified
        return response

def is_valid_path(request, path):
    urlconf = getattr(request, 'urlconf', None)
    try:
        urlresolvers.resolve(path, urlconf)
        return True
    except urlresolvers.Resolver404:
        return False

class RemoveSlashMiddleware(object):
    """
    Middleware that tries to remove a trailing slash if there was a 404.

    If the response is a 404 because url resolution failed, we'll look for a
    better url without a trailing slash.
    """

    def process_response(self, request, response):
        if (response.status_code == 404 and
                request.path_info.endswith('/') and
                not is_valid_path(request, request.path_info) and
                is_valid_path(request, request.path_info[:-1])):
            # Use request.path because we munged app/locale in path_info.
            newurl = request.path[:-1]
            if request.GET:
                with safe_query_string(request):
                    newurl += '?' + request.META['QUERY_STRING']
            return HttpResponsePermanentRedirect(newurl)
        return response

@contextlib.contextmanager
def safe_query_string(request):
    """
    Turn the QUERY_STRING into a unicode- and ascii-safe string.

    We need unicode so it can be combined with a reversed URL, but it has to be
    ascii to go in a Location header.  iri_to_uri seems like a good compromise.
    """
    qs = request.META['QUERY_STRING']
    try:
        request.META['QUERY_STRING'] = iri_to_uri(qs)
        yield
    finally:
        request.META['QUERY_STRING'] = qs

class SetRemoteAddrFromForwardedFor(object):
    """
    Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
    latter is set. This is useful if you're sitting behind a reverse proxy that
    causes each request's REMOTE_ADDR to be set to 127.0.0.1.
    """
    def process_request(self, request):
        try:
            forwarded_for = request.META['HTTP_X_FORWARDED_FOR']
        except KeyError:
            pass
        else:
            # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
            # The client's IP will be the first one.
            forwarded_for = forwarded_for.split(',')[0].strip()
            request.META['REMOTE_ADDR'] = forwarded_for

class ForceAnonymousSessionMiddleware(SessionMiddleware):

    def process_request(self, request):
        """
        Always create an anonymous session.
        """
        request.session = self.SessionStore(None)

    def process_response(self, request, response):
        """
        Override the base-class method to ensure we do nothing.
        """
        return response

class RestrictedEndpointsMiddleware(object):

    def process_request(self, request):
        """
        Restricts the accessible endpoints based on the host.
        """
        if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
            request.urlconf = 'kuma.urls_untrusted'

class RestrictedWhiteNoiseMiddleware(WhiteNoiseMiddleware):

    def process_request(self, request):
        """
        Restricts the use of WhiteNoiseMiddleware based on the host.
        """
        if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
            return None
        return super(RestrictedWhiteNoiseMiddleware, self).process_request(
            request
        )

class LegacyDomainRedirectsMiddleware(object):

    def process_request(self, request):
        """
        Permanently redirects all requests from legacy domains.
        """
        if request.get_host() in settings.LEGACY_HOSTS:
            return HttpResponsePermanentRedirect(
                urljoin(settings.SITE_URL, request.get_full_path())
            )
        return None

class GZipMiddleware(django.middleware.gzip.GZipMiddleware):
    """
    This is identical to Django's GZipMiddleware, except that it will not
    modify the ETag header.

    TODO: When moving to Django 1.11, this code and its tests can be deleted,
          and django.middleware.gzip.GZipMiddleware should be used instead.
    """

    def process_response(self, request, response):
        original_etag = response.get('etag')
        response_out = super(GZipMiddleware, self).process_response(
            request,
            response
        )
        if (original_etag is not None) and response_out.has_header('etag'):
            response_out['etag'] = original_etag
        return response_out
<commit_msg>use hanging indent instead of aligned<commit_after>import contextlib
import urllib
from urlparse import urljoin

import django.middleware.gzip
from django.conf import settings
from django.core import urlresolvers
from django.http import (HttpResponseRedirect, HttpResponseForbidden,
                         HttpResponsePermanentRedirect)
from django.utils import translation
from django.utils.encoding import iri_to_uri, smart_str
from django.contrib.sessions.middleware import SessionMiddleware
from whitenoise.middleware import WhiteNoiseMiddleware

from .urlresolvers import Prefixer, set_url_prefixer, split_path
from .utils import urlparams, is_untrusted
from .views import handler403

class LocaleURLMiddleware(object):
    """
    Based on zamboni.amo.middleware.
    Tried to use localeurl but it choked on 'en-US' with capital letters.

    1. Search for the locale.
    2. Save it in the request.
    3. Strip them from the URL.
    """

    def process_request(self, request):
        prefixer = Prefixer(request)
        set_url_prefixer(prefixer)
        full_path = prefixer.fix(prefixer.shortened_path)
        lang = request.GET.get('lang')

        if lang in dict(settings.LANGUAGES):
            # Blank out the locale so that we can set a new one. Remove lang
            # from the query params so we don't have an infinite loop.
            prefixer.locale = ''
            new_path = prefixer.fix(prefixer.shortened_path)
            query = dict((smart_str(k), v) for
                         k, v in request.GET.iteritems() if k != 'lang')

            # Never use HttpResponsePermanentRedirect here.
            # Its a temporary redirect and should return with http 302, not 301
            return HttpResponseRedirect(urlparams(new_path, **query))

        if full_path != request.path:
            query_string = request.META.get('QUERY_STRING', '')
            full_path = urllib.quote(full_path.encode('utf-8'))

            if query_string:
                full_path = '%s?%s' % (full_path, query_string)

            response = HttpResponseRedirect(full_path)

            # Vary on Accept-Language if we changed the locale
            old_locale = prefixer.locale
            new_locale, _ = split_path(full_path)
            if old_locale != new_locale:
                response['Vary'] = 'Accept-Language'

            return response

        request.path_info = '/' + prefixer.shortened_path
        request.LANGUAGE_CODE = prefixer.locale or settings.LANGUAGE_CODE
        # prefixer.locale can be '', but we need a real locale code to activate
        # otherwise the request uses the previously handled request's
        # translations.
        translation.activate(prefixer.locale or settings.LANGUAGE_CODE)

    def process_response(self, request, response):
        """Unset the thread-local var we set during `process_request`."""
        # This makes mistaken tests (that should use LocalizingClient but
        # use Client instead) fail loudly and reliably. Otherwise, the set
        # prefixer bleeds from one test to the next, making tests
        # order-dependent and causing hard-to-track failures.
        set_url_prefixer(None)
        return response

    def process_exception(self, request, exception):
        set_url_prefixer(None)

class Forbidden403Middleware(object):
    """
    Renders a 403.html page if response.status_code == 403.
    """
    def process_response(self, request, response):
        if isinstance(response, HttpResponseForbidden):
            return handler403(request)
        # If not 403, return response unmodified
        return response

def is_valid_path(request, path):
    urlconf = getattr(request, 'urlconf', None)
    try:
        urlresolvers.resolve(path, urlconf)
        return True
    except urlresolvers.Resolver404:
        return False

class RemoveSlashMiddleware(object):
    """
    Middleware that tries to remove a trailing slash if there was a 404.

    If the response is a 404 because url resolution failed, we'll look for a
    better url without a trailing slash.
    """

    def process_response(self, request, response):
        if (response.status_code == 404 and
                request.path_info.endswith('/') and
                not is_valid_path(request, request.path_info) and
                is_valid_path(request, request.path_info[:-1])):
            # Use request.path because we munged app/locale in path_info.
            newurl = request.path[:-1]
            if request.GET:
                with safe_query_string(request):
                    newurl += '?' + request.META['QUERY_STRING']
            return HttpResponsePermanentRedirect(newurl)
        return response

@contextlib.contextmanager
def safe_query_string(request):
    """
    Turn the QUERY_STRING into a unicode- and ascii-safe string.

    We need unicode so it can be combined with a reversed URL, but it has to be
    ascii to go in a Location header.  iri_to_uri seems like a good compromise.
    """
    qs = request.META['QUERY_STRING']
    try:
        request.META['QUERY_STRING'] = iri_to_uri(qs)
        yield
    finally:
        request.META['QUERY_STRING'] = qs

class SetRemoteAddrFromForwardedFor(object):
    """
    Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
    latter is set. This is useful if you're sitting behind a reverse proxy that
    causes each request's REMOTE_ADDR to be set to 127.0.0.1.
    """
    def process_request(self, request):
        try:
            forwarded_for = request.META['HTTP_X_FORWARDED_FOR']
        except KeyError:
            pass
        else:
            # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
            # The client's IP will be the first one.
            forwarded_for = forwarded_for.split(',')[0].strip()
            request.META['REMOTE_ADDR'] = forwarded_for

class ForceAnonymousSessionMiddleware(SessionMiddleware):

    def process_request(self, request):
        """
        Always create an anonymous session.
        """
        request.session = self.SessionStore(None)

    def process_response(self, request, response):
        """
        Override the base-class method to ensure we do nothing.
        """
        return response

class RestrictedEndpointsMiddleware(object):

    def process_request(self, request):
        """
        Restricts the accessible endpoints based on the host.
        """
        if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
            request.urlconf = 'kuma.urls_untrusted'

class RestrictedWhiteNoiseMiddleware(WhiteNoiseMiddleware):

    def process_request(self, request):
        """
        Restricts the use of WhiteNoiseMiddleware based on the host.
        """
        if settings.ENABLE_RESTRICTIONS_BY_HOST and is_untrusted(request):
            return None
        return super(RestrictedWhiteNoiseMiddleware, self).process_request(
            request
        )

class LegacyDomainRedirectsMiddleware(object):

    def process_request(self, request):
        """
        Permanently redirects all requests from legacy domains.
        """
        if request.get_host() in settings.LEGACY_HOSTS:
            return HttpResponsePermanentRedirect(
                urljoin(settings.SITE_URL, request.get_full_path())
            )
        return None

class GZipMiddleware(django.middleware.gzip.GZipMiddleware):
    """
    This is identical to Django's GZipMiddleware, except that it will not
    modify the ETag header.

    TODO: When moving to Django 1.11, this code and its tests can be deleted,
          and django.middleware.gzip.GZipMiddleware should be used instead.
    """

    def process_response(self, request, response):
        original_etag = response.get('etag')
        response_out = super(GZipMiddleware, self).process_response(
            request,
            response
        )
        if (original_etag is not None) and response_out.has_header('etag'):
            response_out['etag'] = original_etag
        return response_out
<|endoftext|>


<commit_before>"""Utility I/O functions."""

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import json

import numpy as np

# -----------------------------------------------------------------------------
# Data type conversions
# -----------------------------------------------------------------------------
_maxint16 = 2.**15
_maxint16inv = 1./_maxint16
_maxint8 = 127
_maxint8inv = 1./255
_dtype_factors = {
    (np.int16, np.float32): _maxint16inv,
    (np.int16, np.float64): _maxint16inv,
    (np.int32, np.float32): _maxint16inv,
    (np.int32, np.float64): _maxint16inv,
    (np.int64, np.float32): _maxint16inv,
    (np.int64, np.float64): _maxint16inv,
    (np.float32, np.int16): _maxint16,
    (np.float64, np.int16): _maxint16,
    (np.int8, np.float32): _maxint8inv,
    (np.float32, np.int8): _maxint8,
}

def _get_dtype(dtype):
    if isinstance(dtype, np.dtype):
        return dtype.type
    else:
        return dtype

def convert_dtype(data, dtype=None, factor=None):
    if not dtype:
        return data
    if data.shape[0] == 0:
        return data.astype(dtype)
    dtype_old = data.dtype
    if dtype_old == dtype:
        return data
    key = (_get_dtype(dtype_old), _get_dtype(dtype))
    factor = factor or _dtype_factors.get(key, 1)
    # if dtype_old in (np.float32, np.float64):
        # factor = factor/np.abs(data).max()
    # We avoid unnecessary array copy when factor == 1
    if factor != 1:
        return (data * factor).astype(dtype)
    else:
        return data.astype(dtype)

def ensure_vector(data, size=None):
    """Ensure data is a 1D vector."""
    if isinstance(data, (list, tuple)):
        data = np.array(data)
    elif not isinstance(data, np.ndarray):
        data = np.array([data])
    assert data.ndim == 1
    # If size is specified, tile the vector by this size.
    if len(data) == 1 and size is not None:
        data = np.tile(data, size)
    return data

# -----------------------------------------------------------------------------
# JSON functions
# -----------------------------------------------------------------------------
def load_json(path):
    with open(path, 'r') as f:
        return json.load(f)

def save_json(path, d):
    with open(path, 'w') as f:
        json.dump(d, f, indent=4)

<commit_msg>Reverted last commit.<commit_after>"""Utility I/O functions."""

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import json

import numpy as np

# -----------------------------------------------------------------------------
# Data type conversions
# -----------------------------------------------------------------------------
_maxint16 = 2.**12
_maxint16inv = 1./_maxint16
_maxint8 = 127
_maxint8inv = 1./255
_dtype_factors = {
    (np.int16, np.float32): _maxint16inv,
    (np.int16, np.float64): _maxint16inv,
    (np.int32, np.float32): _maxint16inv,
    (np.int32, np.float64): _maxint16inv,
    (np.int64, np.float32): _maxint16inv,
    (np.int64, np.float64): _maxint16inv,
    (np.float32, np.int16): _maxint16,
    (np.float64, np.int16): _maxint16,
    (np.int8, np.float32): _maxint8inv,
    (np.float32, np.int8): _maxint8,
}

def _get_dtype(dtype):
    if isinstance(dtype, np.dtype):
        return dtype.type
    else:
        return dtype

def convert_dtype(data, dtype=None, factor=None):
    if not dtype:
        return data
    if data.shape[0] == 0:
        return data.astype(dtype)
    dtype_old = data.dtype
    if dtype_old == dtype:
        return data
    key = (_get_dtype(dtype_old), _get_dtype(dtype))
    factor = factor or _dtype_factors.get(key, 1)
    if dtype_old in (np.float32, np.float64):
        factor = factor/np.abs(data).max()
    # We avoid unnecessary array copy when factor == 1
    if factor != 1:
        return (data * factor).astype(dtype)
    else:
        return data.astype(dtype)

def ensure_vector(data, size=None):
    """Ensure data is a 1D vector."""
    if isinstance(data, (list, tuple)):
        data = np.array(data)
    elif not isinstance(data, np.ndarray):
        data = np.array([data])
    assert data.ndim == 1
    # If size is specified, tile the vector by this size.
    if len(data) == 1 and size is not None:
        data = np.tile(data, size)
    return data

# -----------------------------------------------------------------------------
# JSON functions
# -----------------------------------------------------------------------------
def load_json(path):
    with open(path, 'r') as f:
        return json.load(f)

def save_json(path, d):
    with open(path, 'w') as f:
        json.dump(d, f, indent=4)

<|endoftext|>


<commit_before>"""Module with main classes related to Interfaces."""
import json
import logging
from enum import IntEnum

from pyof.v0x01.common.phy_port import Port as PortNo01
from pyof.v0x01.common.phy_port import PortFeatures as PortFeatures01
from pyof.v0x04.common.port import PortFeatures as PortFeatures04
from pyof.v0x04.common.port import PortNo as PortNo04

from kytos.core.common import GenericEntity
from kytos.core.helpers import now

__all__ = ('Interface',)

LOG = logging.getLogger(__name__)

class TAGType(IntEnum):
    """Class that represents a TAG Type."""

    VLAN = 1
    VLAN_QINQ = 2
    MPLS = 3

class TAG:
    """Class that represents a TAG."""

    def __init__(self, tag_type, value):
        self.tag_type = TAGType(tag_type)
        self.value = value

    def __eq__(self, other):
        if not other:
            return False
        return self.tag_type == other.tag_type and self.value == other.value

    def as_dict(self):
        """Return a dictionary representating a tag object."""
        return {'tag_type': self.tag_type, 'value': self.value}

    @classmethod
    def from_dict(cls, tag_dict):
        """Return a TAG instance from python dictionary."""
        return cls(tag_dict.get('tag_type'), tag_dict.get('value'))

    @classmethod
    def from_json(cls, tag_json):
        """Return a TAG instance from json."""
        return cls.from_dict(json.loads(tag_json))

    def as_json(self):
        """Return a json representating a tag object."""
        return json.dumps(self.as_dict())

    def __repr__(self):
        return f"TAG({self.tag_type!r}, {self.value!r})"

class Interface(GenericEntity):  # pylint: disable=too-many-instance-attributes
    """Interface Class used to abstract the network interfaces."""

    # pylint: disable=too-many-arguments
    def __init__(self, name, port_number, switch, address=None, state=None,
                 features=None, speed=None):
        """Assign the parameters to instance attributes.

        Args:
            name (string): name from this interface.
            port_number (int): port number from this interface.
            switch (:class:`~.core.switch.Switch`): Switch with this interface.
            address (|hw_address|): Port address from this interface.
            state (|port_stats|): Port Stat from interface. It will be
            deprecated.
            features (|port_features|): Port feature used to calculate link
                utilization from this interface. It will be deprecated.
            speed (int, float): Interface speed in bytes per second. Defaults
                to what is informed by the switch. Return ``None`` if not set
                and switch does not inform the speed.
        """
        self.name = name
        self.port_number = int(port_number)
        self.switch = switch
        self.address = address
        self.state = state
        self.features = features
        self.nni = False
        self.endpoints = []
        self.stats = None
        self.link = None
        self.lldp = True
        self._custom_speed = speed
        self.set_available_tags(range(1, 4096))

        super().__init__()

    def __repr__(self):
        return f"Interface('{self.name}', {self.port_number}, {self.switch!r})"

    def __eq__(self, other):
        """Compare Interface class with another instance."""
        if isinstance(other, str):
            return self.address == other
        if isinstance(other, Interface):
            return self.port_number == other.port_number and \
                self.switch.dpid == other.switch.dpid
        return False

    @property
    def id(self):  # pylint: disable=invalid-name
        """Return id from Interface instance.

        Returns:
            string: Interface id.

        """
        return "{}:{}".format(self.switch.dpid, self.port_number)

    @property
    def uni(self):
        """Return if an interface is a user-to-network Interface."""
        return not self.nni

    def set_available_tags(self, iterable):
        """Set a range of VLAN tags to be used by this Interface.

        Args:
            iterable ([int]): range of VLANs.
        """
        self.available_tags = []

        for i in iterable:
            vlan = TAGType.VLAN
            tag = TAG(vlan, i)
            self.available_tags.append(tag)

    def enable(self):
        """Enable this interface instance.

        Also enable the switch instance this interface is attached to.
        """
        self.switch.enable()
        self._enabled = True

    def use_tag(self, tag):
        """Remove a specific tag from available_tags if it is there.

        Return False in case the tag is already removed.
        """
        try:
            self.available_tags.remove(tag)
        except ValueError:
            return False
        return True

    def is_tag_available(self, tag):
        """Check if a tag is available."""
        return tag in self.available_tags

    def get_next_available_tag(self):
        """Get the next available tag from the interface.

        Return the next available tag if exists and remove from the
        available tags.
        If no tag is available return False.
        """
        try:
            return self.available_tags.pop()
        except IndexError:
            return False

    def make_tag_available(self, tag):
        """Add a specific tag in available_tags."""
        if not self.is_tag_available(tag):
            self.available_tags.append(tag)
            return True
        return False

    def get_endpoint(self, endpoint):
        """Return a tuple with existent endpoint, None otherwise.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.

        Returns:
            tuple: A tuple with endpoint and time of last update.

        """
        for item in self.endpoints:
            if endpoint == item[0]:
                return item
        return None

    def add_endpoint(self, endpoint):
        """Create a new endpoint to Interface instance.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if not exists:
            self.endpoints.append((endpoint, now()))

    def delete_endpoint(self, endpoint):
        """Delete a existent endpoint in Interface instance.

        Args:
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if exists:
            self.endpoints.remove(exists)

    def update_endpoint(self, endpoint):
        """Update or create new endpoint to Interface instance.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if exists:
            self.delete_endpoint(endpoint)
        self.add_endpoint(endpoint)

    def update_link(self, link):
        """Update link for this interface in a consistent way.

        Verify of the other endpoint of the link has the same Link information
        attached to it, and change it if necessary.

        Warning: This method can potentially change information of other
        Interface instances. Use it with caution.
        """
        if self not in (link.endpoint_a, link.endpoint_b):
            return False

        if self.link is None or self.link != link:
            self.link = link

        if link.endpoint_a == self:
            endpoint = link.endpoint_b
        else:
            endpoint = link.endpoint_a

        if endpoint.link is None or endpoint.link != link:
            endpoint.link = link

        return True

    @property
    def speed(self):
        """Return the link speed in bytes per second, None otherwise.

        If the switch was disconnected, we have :attr:`features` and speed is
        still returned for common values between v0x01 and v0x04. For specific
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
        active so we can make sure the protocol version is v0x04.

        Returns:
            int, None: Link speed in bytes per second or ``None``.

        """
        if self._custom_speed is not None:
            return self._custom_speed

        if self._is_v0x04() and self.port_number == PortNo04.OFPP_LOCAL:
            return 0

        if not self._is_v0x04() and self.port_number == PortNo01.OFPP_LOCAL:
            return 0

        return self.get_of_features_speed()

    def set_custom_speed(self, bytes_per_second):
        """Set a speed that overrides switch OpenFlow information.

        If ``None`` is given, :attr:`speed` becomes the one given by the
        switch.
        """
        self._custom_speed = bytes_per_second

    def get_custom_speed(self):
        """Return custom speed or ``None`` if not set."""
        return self._custom_speed

    def get_of_features_speed(self):
        """Return the link speed in bytes per second, None otherwise.

        If the switch was disconnected, we have :attr:`features` and speed is
        still returned for common values between v0x01 and v0x04. For specific
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
        active so we can make sure the protocol version is v0x04.

        Returns:
            int, None: Link speed in bytes per second or ``None``.

        """
        speed = self._get_v0x01_v0x04_speed()
        # Don't use switch.is_connected() because we can have the protocol
        if speed is None and self._is_v0x04():
            speed = self._get_v0x04_speed()
        if speed is not None:
            return speed
        # Warn unknown speed
        # Use shorter switch ID with its beginning and end
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
        else:
            switch_id = self.switch.id
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
                    self.port_number, switch_id, self.features)
        return None

    def _is_v0x04(self):
        """Whether the switch is connected using OpenFlow 1.3."""
        return self.switch.is_connected() and \
            self.switch.connection.protocol.version == 0x04

    def _get_v0x01_v0x04_speed(self):
        """Check against all values of v0x01. They're part of v0x04."""
        fts = self.features
        pfts = PortFeatures01
        if fts and fts & pfts.OFPPF_10GB_FD:
            return 10 * 10**9 / 8
        if fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
            return 10**9 / 8
        if fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
            return 100 * 10**6 / 8
        if fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
            return 10 * 10**6 / 8
        return None

    def _get_v0x04_speed(self):
        """Check against higher enums of v0x04.

        Must be called after :meth:`get_v0x01_speed` returns ``None``.
        """
        fts = self.features
        pfts = PortFeatures04
        if fts and fts & pfts.OFPPF_1TB_FD:
            return 10**12 / 8
        if fts and fts & pfts.OFPPF_100GB_FD:
            return 100 * 10**9 / 8
        if fts and fts & pfts.OFPPF_40GB_FD:
            return 40 * 10**9 / 8
        return None

    def get_hr_speed(self):
        """Return Human-Readable string for link speed.

        Returns:
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.

        """
        speed = self.speed
        if speed is None:
            return ''
        speed *= 8
        if speed == 10**12:
            return '1 Tbps'
        if speed >= 10**9:
            return '{} Gbps'.format(round(speed / 10**9))
        return '{} Mbps'.format(round(speed / 10**6))

    def as_dict(self):
        """Return a dictionary with Interface attributes.

        Speed is in bytes/sec. Example of output (100 Gbps):

        .. code-block:: python3

            {'id': '00:00:00:00:00:00:00:01:2',
             'name': 'eth01',
             'port_number': 2,
             'mac': '00:7e:04:3b:c2:a6',
             'switch': '00:00:00:00:00:00:00:01',
             'type': 'interface',
             'nni': False,
             'uni': True,
             'speed': 12500000000,
             'metadata': {},
             'lldp': True,
             'active': True,
             'enabled': False,
             'link': ""
            }

        Returns:
            dict: Dictionary filled with interface attributes.

        """
        iface_dict = {'id': self.id,
                      'name': self.name,
                      'port_number': self.port_number,
                      'mac': self.address,
                      'switch': self.switch.dpid,
                      'type': 'interface',
                      'nni': self.nni,
                      'uni': self.uni,
                      'speed': self.speed,
                      'metadata': self.metadata,
                      'lldp': self.lldp,
                      'active': self.is_active(),
                      'enabled': self.is_enabled(),
                      'link': self.link.id if self.link else ""}
        if self.stats:
            iface_dict['stats'] = self.stats.as_dict()
        return iface_dict

    def as_json(self):
        """Return a json with Interfaces attributes.

        Example of output:

        .. code-block:: json

            {"mac": "00:7e:04:3b:c2:a6",
             "switch": "00:00:00:00:00:00:00:01",
             "type": "interface",
             "name": "eth01",
             "id": "00:00:00:00:00:00:00:01:2",
             "port_number": 2,
             "speed": "350 Mbps"}

        Returns:
            string: Json filled with interface attributes.

        """
        return json.dumps(self.as_dict())

class UNI:
    """Class that represents an User-to-Network Interface."""

    def __init__(self, interface, user_tag=None):
        self.user_tag = user_tag
        self.interface = interface

    def __eq__(self, other):
        """Override the default implementation."""
        return (self.user_tag == other.user_tag and
                self.interface == other.interface)

    def is_valid(self):
        """Check if TAG is possible for this interface TAG pool."""
        if self.user_tag:
            return self.interface.is_tag_available(self.user_tag)
        return True

    def as_dict(self):
        """Return a dict representating a UNI object."""
        return {
            'interface_id': self.interface.id,
            'tag': self.user_tag.as_dict() if self.user_tag else None
            }

    def as_json(self):
        """Return a json representating a UNI object."""
        return json.dumps(self.as_dict())

class NNI:
    """Class that represents an Network-to-Network Interface."""

    def __init__(self, interface):
        self.interface = interface

class VNNI(NNI):
    """Class that represents an Virtual Network-to-Network Interface."""

    def __init__(self, service_tag, *args, **kwargs):
        self.service_tag = service_tag

        super().__init__(*args, **kwargs)
<commit_msg>Change speed property checks<commit_after>"""Module with main classes related to Interfaces."""
import json
import logging
from enum import IntEnum

from pyof.v0x01.common.phy_port import Port as PortNo01
from pyof.v0x01.common.phy_port import PortFeatures as PortFeatures01
from pyof.v0x04.common.port import PortFeatures as PortFeatures04
from pyof.v0x04.common.port import PortNo as PortNo04

from kytos.core.common import GenericEntity
from kytos.core.helpers import now

__all__ = ('Interface',)

LOG = logging.getLogger(__name__)

class TAGType(IntEnum):
    """Class that represents a TAG Type."""

    VLAN = 1
    VLAN_QINQ = 2
    MPLS = 3

class TAG:
    """Class that represents a TAG."""

    def __init__(self, tag_type, value):
        self.tag_type = TAGType(tag_type)
        self.value = value

    def __eq__(self, other):
        if not other:
            return False
        return self.tag_type == other.tag_type and self.value == other.value

    def as_dict(self):
        """Return a dictionary representating a tag object."""
        return {'tag_type': self.tag_type, 'value': self.value}

    @classmethod
    def from_dict(cls, tag_dict):
        """Return a TAG instance from python dictionary."""
        return cls(tag_dict.get('tag_type'), tag_dict.get('value'))

    @classmethod
    def from_json(cls, tag_json):
        """Return a TAG instance from json."""
        return cls.from_dict(json.loads(tag_json))

    def as_json(self):
        """Return a json representating a tag object."""
        return json.dumps(self.as_dict())

    def __repr__(self):
        return f"TAG({self.tag_type!r}, {self.value!r})"

class Interface(GenericEntity):  # pylint: disable=too-many-instance-attributes
    """Interface Class used to abstract the network interfaces."""

    # pylint: disable=too-many-arguments
    def __init__(self, name, port_number, switch, address=None, state=None,
                 features=None, speed=None):
        """Assign the parameters to instance attributes.

        Args:
            name (string): name from this interface.
            port_number (int): port number from this interface.
            switch (:class:`~.core.switch.Switch`): Switch with this interface.
            address (|hw_address|): Port address from this interface.
            state (|port_stats|): Port Stat from interface. It will be
            deprecated.
            features (|port_features|): Port feature used to calculate link
                utilization from this interface. It will be deprecated.
            speed (int, float): Interface speed in bytes per second. Defaults
                to what is informed by the switch. Return ``None`` if not set
                and switch does not inform the speed.
        """
        self.name = name
        self.port_number = int(port_number)
        self.switch = switch
        self.address = address
        self.state = state
        self.features = features
        self.nni = False
        self.endpoints = []
        self.stats = None
        self.link = None
        self.lldp = True
        self._custom_speed = speed
        self.set_available_tags(range(1, 4096))

        super().__init__()

    def __repr__(self):
        return f"Interface('{self.name}', {self.port_number}, {self.switch!r})"

    def __eq__(self, other):
        """Compare Interface class with another instance."""
        if isinstance(other, str):
            return self.address == other
        if isinstance(other, Interface):
            return self.port_number == other.port_number and \
                self.switch.dpid == other.switch.dpid
        return False

    @property
    def id(self):  # pylint: disable=invalid-name
        """Return id from Interface instance.

        Returns:
            string: Interface id.

        """
        return "{}:{}".format(self.switch.dpid, self.port_number)

    @property
    def uni(self):
        """Return if an interface is a user-to-network Interface."""
        return not self.nni

    def set_available_tags(self, iterable):
        """Set a range of VLAN tags to be used by this Interface.

        Args:
            iterable ([int]): range of VLANs.
        """
        self.available_tags = []

        for i in iterable:
            vlan = TAGType.VLAN
            tag = TAG(vlan, i)
            self.available_tags.append(tag)

    def enable(self):
        """Enable this interface instance.

        Also enable the switch instance this interface is attached to.
        """
        self.switch.enable()
        self._enabled = True

    def use_tag(self, tag):
        """Remove a specific tag from available_tags if it is there.

        Return False in case the tag is already removed.
        """
        try:
            self.available_tags.remove(tag)
        except ValueError:
            return False
        return True

    def is_tag_available(self, tag):
        """Check if a tag is available."""
        return tag in self.available_tags

    def get_next_available_tag(self):
        """Get the next available tag from the interface.

        Return the next available tag if exists and remove from the
        available tags.
        If no tag is available return False.
        """
        try:
            return self.available_tags.pop()
        except IndexError:
            return False

    def make_tag_available(self, tag):
        """Add a specific tag in available_tags."""
        if not self.is_tag_available(tag):
            self.available_tags.append(tag)
            return True
        return False

    def get_endpoint(self, endpoint):
        """Return a tuple with existent endpoint, None otherwise.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.

        Returns:
            tuple: A tuple with endpoint and time of last update.

        """
        for item in self.endpoints:
            if endpoint == item[0]:
                return item
        return None

    def add_endpoint(self, endpoint):
        """Create a new endpoint to Interface instance.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if not exists:
            self.endpoints.append((endpoint, now()))

    def delete_endpoint(self, endpoint):
        """Delete a existent endpoint in Interface instance.

        Args:
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if exists:
            self.endpoints.remove(exists)

    def update_endpoint(self, endpoint):
        """Update or create new endpoint to Interface instance.

        Args:
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
        """
        exists = self.get_endpoint(endpoint)
        if exists:
            self.delete_endpoint(endpoint)
        self.add_endpoint(endpoint)

    def update_link(self, link):
        """Update link for this interface in a consistent way.

        Verify of the other endpoint of the link has the same Link information
        attached to it, and change it if necessary.

        Warning: This method can potentially change information of other
        Interface instances. Use it with caution.
        """
        if self not in (link.endpoint_a, link.endpoint_b):
            return False

        if self.link is None or self.link != link:
            self.link = link

        if link.endpoint_a == self:
            endpoint = link.endpoint_b
        else:
            endpoint = link.endpoint_a

        if endpoint.link is None or endpoint.link != link:
            endpoint.link = link

        return True

    @property
    def speed(self):
        """Return the link speed in bytes per second, None otherwise.

        If the switch was disconnected, we have :attr:`features` and speed is
        still returned for common values between v0x01 and v0x04. For specific
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
        active so we can make sure the protocol version is v0x04.

        Returns:
            int, None: Link speed in bytes per second or ``None``.

        """
        speed = self.get_of_features_speed()

        if speed is not None:
            return speed

        if self._custom_speed is not None:
            return self._custom_speed

        if self._is_v0x04() and self.port_number == PortNo04.OFPP_LOCAL:
            return 0

        if not self._is_v0x04() and self.port_number == PortNo01.OFPP_LOCAL:
            return 0

        # Warn unknown speed
        # Use shorter switch ID with its beginning and end
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
        else:
            switch_id = self.switch.id
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
                    self.port_number, switch_id, self.features)

        return None

    def set_custom_speed(self, bytes_per_second):
        """Set a speed that overrides switch OpenFlow information.

        If ``None`` is given, :attr:`speed` becomes the one given by the
        switch.
        """
        self._custom_speed = bytes_per_second

    def get_custom_speed(self):
        """Return custom speed or ``None`` if not set."""
        return self._custom_speed

    def get_of_features_speed(self):
        """Return the link speed in bytes per second, None otherwise.

        If the switch was disconnected, we have :attr:`features` and speed is
        still returned for common values between v0x01 and v0x04. For specific
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
        active so we can make sure the protocol version is v0x04.

        Returns:
            int, None: Link speed in bytes per second or ``None``.

        """
        speed = self._get_v0x01_v0x04_speed()
        # Don't use switch.is_connected() because we can have the protocol
        if speed is None and self._is_v0x04():
            speed = self._get_v0x04_speed()
        return speed

    def _is_v0x04(self):
        """Whether the switch is connected using OpenFlow 1.3."""
        return self.switch.is_connected() and \
            self.switch.connection.protocol.version == 0x04

    def _get_v0x01_v0x04_speed(self):
        """Check against all values of v0x01. They're part of v0x04."""
        fts = self.features
        pfts = PortFeatures01
        if fts and fts & pfts.OFPPF_10GB_FD:
            return 10 * 10**9 / 8
        if fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
            return 10**9 / 8
        if fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
            return 100 * 10**6 / 8
        if fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
            return 10 * 10**6 / 8
        return None

    def _get_v0x04_speed(self):
        """Check against higher enums of v0x04.

        Must be called after :meth:`get_v0x01_speed` returns ``None``.
        """
        fts = self.features
        pfts = PortFeatures04
        if fts and fts & pfts.OFPPF_1TB_FD:
            return 10**12 / 8
        if fts and fts & pfts.OFPPF_100GB_FD:
            return 100 * 10**9 / 8
        if fts and fts & pfts.OFPPF_40GB_FD:
            return 40 * 10**9 / 8
        return None

    def get_hr_speed(self):
        """Return Human-Readable string for link speed.

        Returns:
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.

        """
        speed = self.speed
        if speed is None:
            return ''
        speed *= 8
        if speed == 10**12:
            return '1 Tbps'
        if speed >= 10**9:
            return '{} Gbps'.format(round(speed / 10**9))
        return '{} Mbps'.format(round(speed / 10**6))

    def as_dict(self):
        """Return a dictionary with Interface attributes.

        Speed is in bytes/sec. Example of output (100 Gbps):

        .. code-block:: python3

            {'id': '00:00:00:00:00:00:00:01:2',
             'name': 'eth01',
             'port_number': 2,
             'mac': '00:7e:04:3b:c2:a6',
             'switch': '00:00:00:00:00:00:00:01',
             'type': 'interface',
             'nni': False,
             'uni': True,
             'speed': 12500000000,
             'metadata': {},
             'lldp': True,
             'active': True,
             'enabled': False,
             'link': ""
            }

        Returns:
            dict: Dictionary filled with interface attributes.

        """
        iface_dict = {'id': self.id,
                      'name': self.name,
                      'port_number': self.port_number,
                      'mac': self.address,
                      'switch': self.switch.dpid,
                      'type': 'interface',
                      'nni': self.nni,
                      'uni': self.uni,
                      'speed': self.speed,
                      'metadata': self.metadata,
                      'lldp': self.lldp,
                      'active': self.is_active(),
                      'enabled': self.is_enabled(),
                      'link': self.link.id if self.link else ""}
        if self.stats:
            iface_dict['stats'] = self.stats.as_dict()
        return iface_dict

    def as_json(self):
        """Return a json with Interfaces attributes.

        Example of output:

        .. code-block:: json

            {"mac": "00:7e:04:3b:c2:a6",
             "switch": "00:00:00:00:00:00:00:01",
             "type": "interface",
             "name": "eth01",
             "id": "00:00:00:00:00:00:00:01:2",
             "port_number": 2,
             "speed": "350 Mbps"}

        Returns:
            string: Json filled with interface attributes.

        """
        return json.dumps(self.as_dict())

class UNI:
    """Class that represents an User-to-Network Interface."""

    def __init__(self, interface, user_tag=None):
        self.user_tag = user_tag
        self.interface = interface

    def __eq__(self, other):
        """Override the default implementation."""
        return (self.user_tag == other.user_tag and
                self.interface == other.interface)

    def is_valid(self):
        """Check if TAG is possible for this interface TAG pool."""
        if self.user_tag:
            return self.interface.is_tag_available(self.user_tag)
        return True

    def as_dict(self):
        """Return a dict representating a UNI object."""
        return {
            'interface_id': self.interface.id,
            'tag': self.user_tag.as_dict() if self.user_tag else None
            }

    def as_json(self):
        """Return a json representating a UNI object."""
        return json.dumps(self.as_dict())

class NNI:
    """Class that represents an Network-to-Network Interface."""

    def __init__(self, interface):
        self.interface = interface

class VNNI(NNI):
    """Class that represents an Virtual Network-to-Network Interface."""

    def __init__(self, service_tag, *args, **kwargs):
        self.service_tag = service_tag

        super().__init__(*args, **kwargs)
<|endoftext|>