Added all common modules in conductor directory
[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.music import api
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         api.MUSIC_API.table_create(**kwargs)
71
72     @abstractclassmethod
73     def atomic(cls):
74         """Use atomic operations"""
75         return False
76
77     @abstractclassmethod
78     def schema(cls):
79         """Return schema"""
80         return cls()
81
82     @abstractclassmethod
83     def pk_name(cls):
84         """Primary key name"""
85         return cls()
86
87     @abstractmethod
88     def pk_value(self):
89         """Primary key value"""
90         pass
91
92     @abstractmethod
93     def values(self):
94         """Values"""
95         pass
96
97     def insert(self):
98         """Insert row"""
99         kwargs = self.__kwargs()
100         kwargs['pk_name'] = self.pk_name()
101         kwargs['values'] = self.values()
102         kwargs['atomic'] = self.atomic()
103         pk_name = kwargs['pk_name']
104         if pk_name not in kwargs['values']:
105             # TODO(jdandrea): Make uuid4() generation a default method in Base.
106             the_id = str(uuid.uuid4())
107             kwargs['values'][pk_name] = the_id
108             kwargs['pk_value'] = the_id
109             setattr(self, pk_name, the_id)
110         else:
111             kwargs['pk_value'] = kwargs['values'][pk_name]
112         api.MUSIC_API.row_create(**kwargs)
113
114     def update(self):
115         """Update row"""
116         kwargs = self.__kwargs()
117         kwargs['pk_name'] = self.pk_name()
118         kwargs['pk_value'] = self.pk_value()
119         kwargs['values'] = self.values()
120         kwargs['atomic'] = self.atomic()
121         # FIXME(jdandrea): Do we need this test/pop clause?
122         pk_name = kwargs['pk_name']
123         if pk_name in kwargs['values']:
124             kwargs['values'].pop(pk_name)
125         api.MUSIC_API.row_update(**kwargs)
126
127     def delete(self):
128         """Delete row"""
129         kwargs = self.__kwargs()
130         kwargs['pk_name'] = self.pk_name()
131         kwargs['pk_value'] = self.pk_value()
132         kwargs['atomic'] = self.atomic()
133         api.MUSIC_API.row_delete(**kwargs)
134
135     @classmethod
136     def filter_by(cls, **kwargs):
137         """Filter objects"""
138         return cls.query.filter_by(**kwargs)  # pylint: disable=E1101
139
140     def flush(self, *args, **kwargs):
141         """Flush changes to storage"""
142         # TODO(jdandrea): Implement in music? May be a no-op
143         pass
144
145     def as_dict(self):
146         """Return object representation as a dictionary"""
147         return dict((k, v) for k, v in self.__dict__.items()
148                     if not k.startswith('_'))
149
150
151 def create_dynamic_model(keyspace, classname, baseclass):
152     """Create a dynamic ORM class with a custom keyspace/class/table.
153
154     Given a keyspace, a camelcase class name, and a base class
155     derived from Base, create a dynamic model that adopts a
156     table name based on a lower-cased version of the class name,
157     then create the table in the keyspace if it doesn't already exist.
158     If the baseclass already has __tablename__ or __keyspace__ set, those
159     will take precedence. Set those to None to use keyspace/classname here.
160     """
161
162     # The comma after baseclass belongs there! Tuple of length 1.
163     model = type(
164         classname, (baseclass,), {
165             '__tablename__': baseclass.__tablename__ or classname.lower(),
166             '__keyspace__': baseclass.__keyspace__ or keyspace})
167     model.table_create()
168     return model