gluon/gluon/particleGenerator/DataBaseModelGenerator.py

178 lines
7.6 KiB
Python

#!/usr/bin/python
# Copyright (c) 2015 Cisco Systems, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import re
import sys
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
class DataBaseModelProcessor(object):
def __init__(self):
self.db_models = dict()
self.data = None
def get_db_models(self, api_name):
return self.db_models.get(api_name)
def add_model(self, model):
self.data = model
def get_table_class(self, api_name, table_name):
try:
return self.db_models.get(api_name)[table_name]
except(TypeError, KeyError):
raise Exception('Unknown table name %s' % table_name)
def build_sqla_models(self, api_name, base=None):
"""Make SQLAlchemy classes for each of the elements in the data read"""
self.db_models[api_name] = dict()
if not base:
base = declarative_base()
if not self.data:
raise Exception('Cannot create Database Model from empty model.')
def de_camel(s):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s)
ret_str = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1)
return ret_str.lower().replace("-", "_")
# Make a model class that we've never thought of before
for table_name, table_data in self.data['api_objects'].items():
self.get_primary_key(table_data)
for table_name, table_data in self.data['api_objects'].items():
try:
attrs = {}
for col_name, col_desc in table_data['attributes'].items():
try:
options = {}
args = []
# Step 1: deal with object xrefs
if col_desc['type'] in self.data['api_objects']:
# This is a foreign key reference. Make the column
# like the FK, but drop the primary from it and
# use the local one.
tgt_name = col_desc['type']
tgt_data = self.data['api_objects'][tgt_name]
primary_col = tgt_data['primary']
repl_col_desc = \
dict(tgt_data['attributes'][primary_col])
if 'primary' in repl_col_desc:
# The FK will be a primary, doesn't mean we are
del repl_col_desc['primary']
# May still be the local PK if we used to be,
# though
if col_desc.get('primary'):
repl_col_desc['primary'] = True
# Set the SQLA col option to make clear what's
# going on
args.append(sa.ForeignKey('%s.%s' %
(de_camel(api_name + "_"
+ tgt_name),
primary_col)))
# The col creation code will now duplicate the FK
# column nicely
col_desc = repl_col_desc
# Step 2: convert our special types to ones a DB likes
if col_desc['type'] == 'uuid':
# UUIDs, from a DB perspective, are a form of
# string
repl_col_desc = dict(col_desc)
repl_col_desc['type'] = 'string'
repl_col_desc['length'] = 64
col_desc = repl_col_desc
# Step 3: with everything DB-ready, spit out the table
# definition
if col_desc.get('primary', False):
options['primary_key'] = True
# Save the information about the primary key
# as well in the object
attrs['_primary_key'] = col_name
required = col_desc.get('required', False)
options['nullable'] = not required
if col_desc['type'] == 'string':
attrs[col_name] = sa.Column(sa.String(
col_desc['length']), *args, **options)
elif col_desc['type'] == 'integer':
attrs[col_name] = sa.Column(sa.Integer(), *args,
**options)
elif col_desc['type'] == 'number':
attrs[col_name] = sa.Column(sa.Float(), *args,
**options)
elif col_desc['type'] == 'boolean':
attrs[col_name] = sa.Column(sa.Boolean(), *args,
**options)
elif col_desc['type'] == 'enum':
attrs[col_name] = sa.Column(
sa.Enum(*col_desc['values']), *args,
**options)
else:
raise Exception('Unknown column type %s' %
col_desc['type'])
except Exception:
print('During processing of attribute ', col_name,
file=sys.stderr)
raise
if '_primary_key' not in attrs:
raise Exception("One and only one primary key has to "
"be given to each column")
attrs['__tablename__'] = de_camel(api_name + "_" + table_name)
class_name = str(api_name + '_' +
table_name).replace("-", "_")
attrs['__name__'] = class_name
attrs['__tname__'] = table_name
attrs['_service_name'] = api_name
self.db_models[api_name][table_name] = type(class_name,
(base,), attrs)
except Exception:
print('During processing of table ', table_name,
file=sys.stderr)
raise
@classmethod
def get_primary_key(cls, table_data):
primary = []
for k, v in table_data['attributes'].items():
if 'primary' in v:
primary = k
break
# If not specified, a UUID is used as the PK
if not primary:
table_data['attributes']['uuid'] = \
{'type': 'string', 'length': 36, 'primary': True,
'required': True}
primary = 'uuid'
table_data['primary'] = primary
return primary