add python compatibility module
[optf/has.git] / conductor / conductor / common / music / model / base.py
1 #
2 # -------------------------------------------------------------------------
3 #   Copyright (c) 2015-2017 AT&T Intellectual Property
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #       http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 #
17 # -------------------------------------------------------------------------
18 #
19
20 """Music ORM - Model"""
21
22 from abc import ABCMeta
23 from abc import abstractmethod
24 import uuid
25
26 from oslo_config import cfg
27 from oslo_log import log as logging
28 import six
29
30 from conductor.common.classes import abstractclassmethod
31 from conductor.common.classes import classproperty
32 from conductor.common import db_backend
33 from conductor.common.music.model import search
34
35 LOG = logging.getLogger(__name__)
36
37 CONF = cfg.CONF
38
39
40 @six.add_metaclass(ABCMeta)
41 class Base(object):
42     """A custom declarative base ORM-style class.
43
44     Provides some Elixir-inspired shortcuts as well.
45     """
46
47     # These must be set in the derived class!
48     __tablename__ = None
49     __keyspace__ = None
50
51     @classproperty
52     def query(cls):  # pylint: disable=E0213
53         """Return a query object a la sqlalchemy"""
54         return search.Query(cls)
55
56     @classmethod
57     def __kwargs(cls):
58         """Return common keyword args"""
59         kwargs = {
60             'keyspace': cls.__keyspace__,
61             'table': cls.__tablename__,
62         }
63         return kwargs
64
65     @classmethod
66     def table_create(cls):
67         """Create table"""
68         kwargs = cls.__kwargs()
69         kwargs['schema'] = cls.schema()
70         db_backend.DB_API.table_create(**kwargs)
71
72         # Create indexes for the table
73         del kwargs['schema']
74         if cls.indexes():
75             for index in cls.indexes():
76                 kwargs['index'] = index
77                 db_backend.DB_API.index_create(**kwargs)
78
79     @abstractclassmethod
80     def atomic(cls):
81         """Use atomic operations"""
82         return False
83
84     @abstractclassmethod
85     def schema(cls):
86         """Return schema"""
87         return cls()
88
89     @classmethod
90     def indexes(cls):
91         """Return Indexes"""
92         pass
93         # return cls()
94
95     @abstractclassmethod
96     def pk_name(cls):
97         """Primary key name"""
98         return cls()
99
100     @abstractmethod
101     def pk_value(self):
102         """Primary key value"""
103         pass
104
105     @abstractmethod
106     def values(self):
107         """Values"""
108         pass
109
110     def insert(self):
111         """Insert row"""
112         kwargs = self.__kwargs()
113         kwargs['pk_name'] = self.pk_name()
114         kwargs['values'] = self.values()
115         kwargs['atomic'] = self.atomic()
116         pk_name = kwargs['pk_name']
117
118         if pk_name not in kwargs['values']:
119             # TODO(jdandrea): Make uuid4() generation a default method in Base.
120             the_id = str(uuid.uuid4())
121             kwargs['values'][pk_name] = the_id
122             kwargs['pk_value'] = the_id
123             setattr(self, pk_name, the_id)
124         else:
125             kwargs['pk_value'] = kwargs['values'][pk_name]
126         response = db_backend.DB_API.row_create(**kwargs)
127         return response
128
129     def update(self, condition=None):
130         """Update row"""
131         kwargs = self.__kwargs()
132         kwargs['pk_name'] = self.pk_name()
133         kwargs['pk_value'] = self.pk_value()
134         kwargs['values'] = self.values()
135
136         # In active-active, all update operations should be atomic
137         kwargs['atomic'] = True
138         kwargs['condition'] = condition
139         # FIXME(jdandrea): Do we need this test/pop clause?
140         pk_name = kwargs['pk_name']
141         if kwargs['table'] != ('order_locks'):
142             if pk_name in kwargs['values']:
143                 kwargs['values'].pop(pk_name)
144         return db_backend.DB_API.row_update(**kwargs)
145
146     def delete(self):
147         """Delete row"""
148         kwargs = self.__kwargs()
149         kwargs['pk_name'] = self.pk_name()
150         kwargs['pk_value'] = self.pk_value()
151         kwargs['atomic'] = self.atomic()
152         db_backend.DB_API.row_delete(**kwargs)
153
154     @classmethod
155     def filter_by(cls, **kwargs):
156         """Filter objects"""
157         return cls.query.filter_by(**kwargs)  # pylint: disable=E1101
158
159     def flush(self, *args, **kwargs):
160         """Flush changes to storage"""
161         # TODO(jdandrea): Implement in music? May be a no-op
162         pass
163
164     def as_dict(self):
165         """Return object representation as a dictionary"""
166         return dict((k, v) for k, v in self.__dict__.items()
167                     if not k.startswith('_'))
168
169
170 def create_dynamic_model(keyspace, classname, baseclass):
171     """Create a dynamic ORM class with a custom keyspace/class/table.
172
173     Given a keyspace, a camelcase class name, and a base class
174     derived from Base, create a dynamic model that adopts a
175     table name based on a lower-cased version of the class name,
176     then create the table in the keyspace if it doesn't already exist.
177     If the baseclass already has __tablename__ or __keyspace__ set, those
178     will take precedence. Set those to None to use keyspace/classname here.
179     """
180
181     # The comma after baseclass belongs there! Tuple of length 1.
182     model = type(
183         classname, (baseclass,), {
184             '__tablename__': baseclass.__tablename__ or classname.lower(),
185             '__keyspace__': baseclass.__keyspace__ or keyspace})
186     model.table_create()
187     return model