def validate(definition: SchemaDefinition): try: definition.validate() except ValidationError as e: return SymlServiceResponse( data=dict( message=e.message, validator=e.validator, validator_value=e.validator_value, absolute_path=list(e.absolute_path), instance=e.instance, ), errors=[ dict( message="validation failed ${file}", file=definition.path ) ] ) return SymlServiceResponse( data={'status': 'ok'}, info=[ dict(message="validated ${file}", file=definition.path) ] )
async def cmd_alias(self, cmd: SymlServiceCommand[ProfileSetAlias]): profile_name = cmd.args.profile_name alias_name = cmd.args.alias_name alias_val = cmd.args.alias_val manifest = self.load_profile_manifest(profile_name) if isinstance(manifest, SymlServiceResponse): return manifest aliases = manifest.get('aliases') if alias_val is not None: aliases[alias_name] = alias_val else: if alias_name in aliases: del aliases[alias_name] else: return SymlServiceResponse(errors=[ dict( message="unable to find the alias {alias}", alias=alias_name, ) ]) return SymlServiceResponse(info=[ dict( message="alias {alias} set to {value}", alias=alias_name, value=alias_val, ) if alias_val is not None else dict( message="alias {alias} deleted", alias=alias_name, ) ]).combined_with(self.store_profile_manifest(profile_name, manifest))
async def cmd_get(self, cmd: SymlServiceCommand[GetSchemaParams]): path = cmd.args.path validate = cmd.args.validate definition = SchemaDefinition(self.spec, path) return SymlServiceResponse.merge( definition=SymlServiceResponse( data=definition.body, info=[ dict(message="loaded ${file}", file=definition.path) ] ), validation=self.validate(definition) if validate else None, )
async def cmd_create(self, cmd: SymlServiceCommand[CreateProfile]): base = cmd.args.base profile_name = cmd.args.profile_name use_default = base is None if base is None: base = self.DEFAULT_BASE base = str(base).replace('@profiles', str(self.DEFAULT_PROFILES_STORAGE_PATH)) base = Path(base).expanduser().resolve() base_profile_manifest_path = base / self.PROFILE_FILENAME if base_profile_manifest_path.exists(): with open(str(base_profile_manifest_path), 'r') as f: base_profile_manifest = yaml.load(f, Loader=yaml.SafeLoader) elif use_default: base_profile_manifest = dict( aliases={}, areas={}, # TODO: better defaults ) self.store_profile_manifest(profile_name, base_profile_manifest) else: return SymlServiceResponse(errors=[ dict( message='unable to find base {base} for profile {profile}', base=base, profile=profile_name) ]) return self.store_profile_manifest(profile_name, base_profile_manifest)
def store_profile_manifest(self, name, body): path = self.resolve_profile_manifest_path(name) is_new = 'created_at' not in body path.parent.mkdir(parents=True, exist_ok=True) with open(str(path), 'w') as profile_file: yaml.dump( { **body, 'created_at': body.get('created_at', datetime.now()), 'updated_at': datetime.now(), }, profile_file) # TODO: error handling return SymlServiceResponse( data=body, info=[ dict( message='profile {profile} created' if is_new else 'profile {profile} updated', profile=name, ) ])
async def cmd_alchemy(self, cmd: SymlServiceCommand[ReverseUsingAlchemy]): connection_string = cmd.args.connection_string schemas = cmd.args.schemas objects_names = cmd.args.objects_names objects_types = cmd.args.objects_types if connection_string == '@postgres': connection_string = "postgresql://*****:*****@localhost:5432/symltest" # TODO: postgres in the docker locally or something like that? # https://pypi.org/project/pyembedpg/ kinda sucks :( engine = create_engine(connection_string) if schemas == '@all': # TODO: adjust this if necessary with the schema filter pass metadata = MetaData(engine) # TODO: check if we can get schema list instead #self.metadata = MetaData(engine, schema=schema) # TODO: support for views, and other object types if objects_names != '@all': metadata.reflect(only=objects_names.split(',')) else: metadata.reflect() # TODO: filter tables by schema name maybe? probably not gonna work :( return SymlServiceResponse( data=self.get_transformed_tables(metadata), )
async def cmd_list(self): await self.ensure() items = [] for d in self.DEFAULT_PROFILES_STORAGE_PATH.iterdir(): # TODO: more specific data profile_manifest = self.load_profile_manifest(d.name) item = dict(name=d.name, meta=dict( created_at=profile_manifest.get('created_at'), updated_at=profile_manifest.get('updated_at'), )) items.append(item) return SymlServiceResponse(data=dict(items=items))
def load_profile_manifest(self, name): path = self.resolve_profile_manifest_path(name) if path.exists(): with open(str(path), 'r') as f: return yaml.load(f, Loader=yaml.SafeLoader) else: return SymlServiceResponse(errors=[ dict( message='unable to find profile {profile} ' 'in the path {path}', profile=name, path=str(path), ) ])
async def on_client_connected(self, reader: StreamReader, writer: StreamWriter): self.logger.debug('client connected') while True: # TODO: while active instead raw_command = await reader.readline() if raw_command == b'': # TODO: not ideal, remove pooling if possible await asyncio.sleep(0.5) continue try: command = SymlServiceCommand.parse(raw_command) logging.debug("received command %s", command) callable_command = getattr(self, f'cmd_{command.name}') cmd_arg = get_type_hints(callable_command).get('cmd') if cmd_arg: args_type = get_args(cmd_arg)[0] command.args = args_type(**command.args) # TODO: handle generators try: response: SymlServiceResponse if cmd_arg: response = await callable_command(command) else: response = await callable_command() except Exception as e: exc_type, exc_value, traceback = sys.exc_info() tb = Traceback() trace = tb.extract(exc_type, exc_value, traceback, show_locals=False) trace = [dataclasses.asdict(s) for s in trace.stacks] trace = [{ **t, 'frames': t.get('frames')[1:] } for t in trace] response = SymlServiceResponse( data=dict(), errors=[ dict(message="unhandled exception while " "processing command (${exception})", exception=e, trace=trace) ]) response.command = command writer.write(response.jsonb()) writer.write('\n'.encode()) logging.debug("sending response %s", response) await writer.drain() except Exception as e: self.logger.exception("oh no", e)