#!/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