PK!2Bxxschemainspect/__init__.pyfrom __future__ import absolute_import, division, print_function, unicode_literals from . import pg from .get import get_inspector from .inspected import ColumnInfo, Inspected from .inspector import DBInspector, NullInspector, to_pytype __all__ = [ "DBInspector", "to_pytype", "ColumnInfo", "Inspected", "get_inspector", "pg", "NullInspector", ] PK!1schemainspect/get.pyfrom .inspector import NullInspector from .misc import connection_from_s_or_c from .pg import PostgreSQL SUPPORTED = {"postgresql": PostgreSQL} def get_inspector(x, schema=None): if x is None: return NullInspector() c = connection_from_s_or_c(x) try: ic = SUPPORTED[c.dialect.name] except KeyError: raise NotImplementedError inspected = ic(c) if schema: inspected.one_schema(schema) return inspected PK!#schemainspect/inspected.pyfrom collections import OrderedDict as od from .misc import AutoRepr, quoted_identifier class Inspected(AutoRepr): @property def quoted_full_name(self): return "{}.{}".format( quoted_identifier(self.schema), quoted_identifier(self.name) ) @property def signature(self): return self.quoted_full_name @property def unquoted_full_name(self): return "{}.{}".format(self.schema, self.name) @property def quoted_name(self): return quoted_identifier(self.name) @property def quoted_schema(self): return quoted_identifier(self.schema) def __ne__(self, other): return not self == other class TableRelated(object): @property def quoted_full_table_name(self): return "{}.{}".format( quoted_identifier(self.schema), quoted_identifier(self.table_name) ) class ColumnInfo(AutoRepr): def __init__( self, name, dbtype, pytype, default=None, not_null=False, is_enum=False, enum=None, dbtypestr=None, collation=None, ): self.name = name or "" self.dbtype = dbtype self.dbtypestr = dbtypestr or dbtype self.pytype = pytype self.default = default or None self.not_null = not_null self.is_enum = is_enum self.enum = enum self.collation = collation def __eq__(self, other): return ( self.name == other.name and self.dbtype == other.dbtype and self.dbtypestr == other.dbtypestr and self.pytype == other.pytype and self.default == other.default and self.not_null == other.not_null and self.enum == other.enum and self.collation == other.collation ) def alter_clauses(self, other): clauses = [] if self.default != other.default: clauses.append(self.alter_default_clause) if self.not_null != other.not_null: clauses.append(self.alter_not_null_clause) if self.dbtypestr != other.dbtypestr or self.collation != other.collation: clauses.append(self.alter_data_type_clause) return clauses def change_enum_to_string_statement(self, table_name): if self.is_enum: return "alter table {} alter column {} set data type varchar using {}::varchar;".format( table_name, self.quoted_name, self.quoted_name ) else: raise ValueError def change_string_to_enum_statement(self, table_name): if self.is_enum: return "alter table {} alter column {} set data type {} using {}::{};".format( table_name, self.quoted_name, self.dbtypestr, self.quoted_name, self.dbtypestr, ) else: raise ValueError def alter_table_statements(self, other, table_name): prefix = "alter table {}".format(table_name) return ["{} {};".format(prefix, c) for c in self.alter_clauses(other)] @property def quoted_name(self): return quoted_identifier(self.name) @property def creation_clause(self): x = "{} {}".format(self.quoted_name, self.dbtypestr) if self.not_null: x += " not null" if self.default: x += " default {}".format(self.default) return x @property def add_column_clause(self): return "add column {}{}".format(self.creation_clause, self.collation_subclause) @property def drop_column_clause(self): return "drop column {k}".format(k=self.quoted_name) @property def alter_not_null_clause(self): keyword = "set" if self.not_null else "drop" return "alter column {} {} not null".format(self.quoted_name, keyword) @property def alter_default_clause(self): if self.default: alter = "alter column {} set default {}".format( self.quoted_name, self.default ) else: alter = "alter column {} drop default".format(self.quoted_name) return alter @property def collation_subclause(self): if self.collation: collate = " collate {}".format(quoted_identifier(self.collation)) else: collate = "" return collate @property def alter_data_type_clause(self): return "alter column {} set data type {}{} using {}::{}".format( self.quoted_name, self.dbtypestr, self.collation_subclause, self.quoted_name, self.dbtypestr, ) class InspectedSelectable(Inspected): def __init__( self, name, schema, columns, inputs=None, definition=None, dependent_on=None, dependents=None, comment=None, relationtype="unknown", parent_table=None, partition_def=None, rowsecurity=False, forcerowsecurity=False, ): self.name = name self.schema = schema self.inputs = inputs or [] self.columns = columns self.definition = definition self.relationtype = relationtype self.dependent_on = dependent_on or [] self.dependents = dependents or [] self.dependent_on_all = [] self.dependents_all = [] self.constraints = od() self.indexes = od() self.comment = comment self.parent_table = parent_table self.partition_def = partition_def self.rowsecurity = rowsecurity self.forcerowsecurity = forcerowsecurity def __eq__(self, other): equalities = ( type(self) == type(other), self.relationtype == other.relationtype, self.name == other.name, self.schema == other.schema, dict(self.columns) == dict(other.columns), self.inputs == other.inputs, self.definition == other.definition, self.parent_table == other.parent_table, self.partition_def == other.partition_def, self.rowsecurity == other.rowsecurity, ) return all(equalities) PK!ETqWWschemainspect/inspector.pyfrom __future__ import absolute_import, division, print_function, unicode_literals from collections import OrderedDict as od def to_pytype(sqla_dialect, typename): try: sqla_obj = sqla_dialect.ischema_names[typename]() except KeyError: return type(None) try: return sqla_obj.python_type except (NotImplementedError): return type(sqla_obj) class DBInspector(object): def __init__(self, c, include_internal=False): self.c = c self.engine = self.c.engine self.dialect = self.engine.dialect self.include_internal = include_internal self.load_all() def to_pytype(self, typename): return to_pytype(self.dialect, typename) class NullInspector(DBInspector): def __init__(self): pass def __getattr__(self, name): return od() PK!6͌schemainspect/misc.pyimport inspect import six from pkg_resources import resource_stream as pkg_resource_stream def connection_from_s_or_c(s_or_c): # pragma: no cover try: s_or_c.engine return s_or_c except AttributeError: return s_or_c.connection() @six.python_2_unicode_compatible class AutoRepr(object): # pragma: no cover def __repr__(self): cname = self.__class__.__name__ vals = [ "{}={}".format(k, repr(v)) for k, v in sorted(self.__dict__.items()) if not k.startswith("_") ] return "{}({})".format(cname, ", ".join(vals)) def __str__(self): return repr(self) def __ne__(self, other): return not self == other def quoted_identifier(identifier, schema=None): try: identifier, remainder = identifier.split("(", 1) remainder = "(" + remainder except ValueError: remainder = "" s = '"{}"'.format(identifier.replace('"', '""')) if schema: s = '"{}".{}'.format(schema.replace('"', '""'), s) return s + remainder def external_caller(): i = inspect.stack() names = (inspect.getmodule(i[x][0]).__name__ for x in range(len(i))) return next(name for name in names if name != __name__) def resource_stream(subpath): module_name = external_caller() return pkg_resource_stream(module_name, subpath) def resource_text(subpath): with resource_stream(subpath) as f: return f.read().decode("utf-8") PK!A7H>>schemainspect/pg/__init__.pyfrom . import obj # noqa from .obj import PostgreSQL # noqa PK!bschemainspect/pg/obj.pyfrom __future__ import absolute_import, division, print_function, unicode_literals from collections import OrderedDict as od from itertools import groupby from sqlalchemy import text from ..inspected import ColumnInfo, Inspected from ..inspected import InspectedSelectable as BaseInspectedSelectable from ..inspected import TableRelated from ..inspector import DBInspector from ..misc import quoted_identifier, resource_text CREATE_TABLE = """create table {} ({} ){}; """ CREATE_TABLE_SUBCLASS = """create table partition of {} {}; """ CREATE_FUNCTION_FORMAT = """create or replace function {signature} returns {result_string} as $${definition}$$ language {language} {volatility} {strictness} {security_type};""" ALL_RELATIONS_QUERY = resource_text("sql/relations.sql") SCHEMAS_QUERY = resource_text("sql/schemas.sql") INDEXES_QUERY = resource_text("sql/indexes.sql") SEQUENCES_QUERY = resource_text("sql/sequences.sql") CONSTRAINTS_QUERY = resource_text("sql/constraints.sql") FUNCTIONS_QUERY = resource_text("sql/functions.sql") EXTENSIONS_QUERY = resource_text("sql/extensions.sql") ENUMS_QUERY = resource_text("sql/enums.sql") DEPS_QUERY = resource_text("sql/deps.sql") PRIVILEGES_QUERY = resource_text("sql/privileges.sql") TRIGGERS_QUERY = resource_text("sql/triggers.sql") COLLATIONS_QUERY = resource_text("sql/collations.sql") RLSPOLICIES_QUERY = resource_text("sql/rlspolicies.sql") class InspectedSelectable(BaseInspectedSelectable): def has_compatible_columns(self, other): items = list(self.columns.items()) if self.relationtype != "f": old_arg_count = len(other.columns) items = items[:old_arg_count] items = od(items) return items == other.columns def can_replace(self, other): if not (self.relationtype in ("v", "f") or self.is_table): return False if self.signature != other.signature: return False if self.relationtype != other.relationtype: return False return self.has_compatible_columns(other) @property def create_statement(self): n = self.quoted_full_name if self.relationtype in ("r", "p"): if self.parent_table is None: colspec = ",\n".join( " " + c.creation_clause for c in self.columns.values() ) if colspec: colspec = "\n" + colspec if self.partition_def: partition_key = " partition by " + self.partition_def else: partition_key = "" create_statement = CREATE_TABLE.format(n, colspec, partition_key) else: create_statement = CREATE_TABLE_SUBCLASS.format( self.parent_table, self.partition_def ) elif self.relationtype == "v": create_statement = "create or replace view {} as {}\n".format( n, self.definition ) elif self.relationtype == "m": create_statement = "create materialized view {} as {}\n".format( n, self.definition ) elif self.relationtype == "c": colspec = ", ".join(c.creation_clause for c in self.columns.values()) create_statement = "create type {} as ({});".format(n, colspec) else: raise NotImplementedError # pragma: no cover return create_statement @property def drop_statement(self): n = self.quoted_full_name if self.relationtype in ("r", "p"): drop_statement = "drop table {};".format(n) elif self.relationtype == "v": drop_statement = "drop view if exists {};".format(n) elif self.relationtype == "m": drop_statement = "drop materialized view if exists {};".format(n) elif self.relationtype == "c": drop_statement = "drop type {};".format(n) else: raise NotImplementedError # pragma: no cover return drop_statement def alter_table_statement(self, clause): if self.is_alterable: alter = "alter table {} {};".format(self.quoted_full_name, clause) else: raise NotImplementedError # pragma: no cover return alter @property def is_partitioned(self): return self.relationtype == "p" @property def is_table(self): return self.relationtype in ("p", "r") @property def is_alterable(self): return self.is_table and not self.parent_table @property def contains_data(self): return bool( self.relationtype == "r" and (self.parent_table or not self.partition_def) ) @property def is_child_table(self): return bool(self.relationtype == "r" and self.parent_table) @property def uses_partitioning(self): return self.is_child_table or self.is_partitioned @property def attach_statement(self): if self.parent_table: return "alter table {} attach partition {} {};".format( self.quoted_full_name, self.parent_table, self.partition_spec ) @property def detach_statement(self): if self.parent_table: return "alter table {} detach partition {};".format( self.parent_table, self.quoted_full_name ) def attach_detach_statements(self, before): slist = [] if self.parent_table != before.parent_table: if before.parent_table: slist.append(before.detach_statement) if self.parent_table: slist.append(self.attach_statement) return slist @property def alter_rls_clause(self): keyword = "enable" if self.rowsecurity else "disable" return "{} row level security".format(keyword) @property def alter_rls_statement(self): return self.alter_table_statement(self.alter_rls_clause) class InspectedFunction(InspectedSelectable): def __init__( self, name, schema, columns, inputs, definition, volatility, strictness, security_type, identity_arguments, result_string, language, full_definition, comment, ): self.identity_arguments = identity_arguments self.result_string = result_string self.language = language self.volatility = volatility self.strictness = strictness self.security_type = security_type self.full_definition = full_definition super(InspectedFunction, self).__init__( name=name, schema=schema, columns=columns, inputs=inputs, definition=definition, relationtype="f", comment=comment, ) @property def signature(self): return "{}({})".format(self.quoted_full_name, self.identity_arguments) @property def create_statement(self): return self.full_definition + ";" """ return CREATE_FUNCTION_FORMAT.format( signature=self.signature, result_string=self.result_string, definition=self.definition, language=self.language, volatility=self.volatility, strictness=self.strictness, security_type=self.security_type, ) """ @property def drop_statement(self): return "drop function if exists {};".format(self.signature) def __eq__(self, other): return ( self.signature == other.signature and self.result_string == other.result_string and self.definition == other.definition and self.language == other.language and self.volatility == other.volatility and self.strictness == other.strictness and self.security_type == other.security_type ) class InspectedTrigger(Inspected): def __init__( self, name, schema, table_name, proc_schema, proc_name, enabled, full_definition ): self.name, self.schema, self.table_name, self.proc_schema, self.proc_name, self.enabled, self.full_definition = ( name, schema, table_name, proc_schema, proc_name, enabled, full_definition, ) @property def quoted_full_name(self): return "{}.{}.{}".format( quoted_identifier(self.schema), quoted_identifier(self.table_name), quoted_identifier(self.name), ) @property def drop_statement(self): return 'drop trigger if exists "{}" on "{}"."{}";'.format( self.name, self.schema, self.table_name ) @property def create_statement(self): return self.full_definition + ";" def __eq__(self, other): """ :type other: InspectedTrigger :rtype: bool """ return ( self.name == other.name and self.schema == other.schema and self.table_name == other.table_name and self.proc_schema == other.proc_schema and self.proc_name == other.proc_name and self.enabled == other.enabled and self.full_definition == other.full_definition ) class InspectedIndex(Inspected, TableRelated): def __init__( self, name, schema, table_name, key_columns, key_options, num_att, is_unique, is_pk, is_exclusion, is_immediate, is_clustered, key_collations, key_expressions, partial_predicate, definition=None, ): self.name = name self.schema = schema self.definition = definition self.table_name = table_name self.key_columns = key_columns self.key_options = key_options self.num_att = num_att self.is_unique = is_unique self.is_pk = is_pk self.is_exclusion = is_exclusion self.is_immediate = is_immediate self.is_clustered = is_clustered self.key_collations = key_collations self.key_expressions = key_expressions self.partial_predicate = partial_predicate @property def drop_statement(self): return "drop index if exists {};".format(self.quoted_full_name) @property def create_statement(self): return "{};".format(self.definition) def __eq__(self, other): """ :type other: InspectedIndex :rtype: bool """ equalities = ( self.name == other.name, self.schema == other.schema, self.table_name == other.table_name, self.key_columns == other.key_columns, self.key_options == other.key_options, self.num_att == other.num_att, self.is_unique == other.is_unique, self.is_pk == other.is_pk, self.is_exclusion == other.is_exclusion, self.is_immediate == other.is_immediate, self.is_clustered == other.is_clustered, self.key_collations == other.key_collations, self.key_expressions == other.key_expressions, self.partial_predicate == other.partial_predicate, ) return all(equalities) class InspectedSequence(Inspected): def __init__(self, name, schema): self.name = name self.schema = schema @property def drop_statement(self): return "drop sequence if exists {};".format(self.quoted_full_name) @property def create_statement(self): return "create sequence {};".format(self.quoted_full_name) def __eq__(self, other): equalities = self.name == other.name, self.schema == other.schema return all(equalities) class InspectedCollation(Inspected): def __init__(self, name, schema, provider, encoding, lc_collate, lc_ctype, version): self.name = name self.schema = schema self.provider = provider self.lc_collate = lc_collate self.lc_ctype = lc_ctype self.encoding = encoding self.version = version @property def locale(self): return self.lc_collate @property def drop_statement(self): return "drop collation if exists {};".format(self.quoted_full_name) @property def create_statement(self): return "create collation if not exists {} (provider = '{}', locale = '{}');".format( self.quoted_full_name, self.provider, self.locale ) def __eq__(self, other): equalities = ( self.name == other.name, self.schema == other.schema, self.provider == other.provider, self.locale == other.locale, ) return all(equalities) class InspectedEnum(Inspected): def __init__(self, name, schema, elements): self.name = name self.schema = schema self.elements = elements @property def drop_statement(self): return "drop type {};".format(self.quoted_full_name) @property def create_statement(self): return "create type {} as enum ({});".format( self.quoted_full_name, self.quoted_elements ) @property def quoted_elements(self): quoted = ["'{}'".format(e) for e in self.elements] return ", ".join(quoted) def change_statements(self, new): if not self.can_be_changed_to(new): raise ValueError new = new.elements old = self.elements statements = [] previous = None for c in new: if c not in old: if not previous: s = "alter type {} add value '{}' before '{}'".format( self.quoted_full_name, c, old[0] ) else: s = "alter type {} add value '{}' after '{}'".format( self.quoted_full_name, c, previous ) statements.append(s) previous = c return statements def can_be_changed_to(self, new): old = self.elements # new must already have the existing items from old, in the same order return [e for e in new.elements if e in old] == old def __eq__(self, other): equalities = ( self.name == other.name, self.schema == other.schema, self.elements == other.elements, ) return all(equalities) class InspectedSchema(Inspected): def __init__(self, schema): self.schema = schema self.name = None @property def create_statement(self): return "create schema if not exists {};".format(self.quoted_schema) @property def drop_statement(self): return "drop schema if exists {};".format(self.quoted_schema) def __eq__(self, other): return self.schema == other.schema class InspectedExtension(Inspected): def __init__(self, name, schema, version): self.name = name self.schema = schema self.version = version @property def drop_statement(self): return "drop extension if exists {};".format(self.quoted_name) @property def create_statement(self): return "create extension if not exists {} with schema {} version '{}';".format( self.quoted_name, self.quoted_schema, self.version ) @property def update_statement(self): return "alter extension {} update to version '{}';".format( self.quoted_full_name, self.version ) def __eq__(self, other): equalities = ( self.name == other.name, self.schema == other.schema, self.version == other.version, ) return all(equalities) class InspectedConstraint(Inspected, TableRelated): def __init__(self, name, schema, constraint_type, table_name, definition, index): self.name = name self.schema = schema self.constraint_type = constraint_type self.table_name = table_name self.definition = definition self.index = index @property def drop_statement(self): return "alter table {} drop constraint {};".format( self.quoted_full_table_name, self.quoted_name ) @property def create_statement(self): USING = "alter table {} add constraint {} {} using index {};" NOT_USING = "alter table {} add constraint {} {};" if self.index: return USING.format( self.quoted_full_table_name, self.quoted_name, self.constraint_type, self.quoted_name, ) else: return NOT_USING.format( self.quoted_full_table_name, self.quoted_name, self.definition ) @property def quoted_full_name(self): return "{}.{}.{}".format( quoted_identifier(self.schema), quoted_identifier(self.table_name), quoted_identifier(self.name), ) def __eq__(self, other): equalities = ( self.name == other.name, self.schema == other.schema, self.table_name == other.table_name, self.definition == other.definition, self.index == other.index, ) return all(equalities) class InspectedPrivilege(Inspected): def __init__(self, object_type, schema, name, privilege, target_user): self.schema = schema self.object_type = object_type self.name = name self.privilege = privilege.lower() self.target_user = target_user @property def drop_statement(self): return "revoke {} on {} {} from {};".format( self.privilege, self.object_type, self.quoted_full_name, self.target_user ) @property def create_statement(self): return "grant {} on {} {} to {};".format( self.privilege, self.object_type, self.quoted_full_name, self.target_user ) def __eq__(self, other): equalities = ( self.schema == other.schema, self.object_type == other.object_type, self.name == other.name, self.privilege == other.privilege, self.target_user == other.target_user, ) return all(equalities) @property def key(self): return self.object_type, self.quoted_full_name, self.target_user, self.privilege RLS_POLICY_CREATE = """create policy {name} on {table_name} as {permissiveness} for {commandtype_keyword} to {roleslist}{qual_clause}{withcheck_clause}; """ COMMANDTYPES = {"*": "all", "r": "select", "a": "insert", "w": "update", "d": "delete"} class InspectedRowPolicy(Inspected, TableRelated): def __init__( self, name, schema, table_name, commandtype, permissive, roles, qual, withcheck ): self.name = name self.schema = schema self.table_name = table_name self.commandtype = commandtype self.permissive = permissive self.roles = roles self.qual = qual self.withcheck = withcheck @property def permissiveness(self): return "permissive" if self.permissive else "restrictive" @property def commandtype_keyword(self): return COMMANDTYPES[self.commandtype] @property def key(self): return "{}.{}".format(self.quoted_full_table_name, self.quoted_name) @property def create_statement(self): if self.qual: qual_clause = "\nusing {}".format(self.qual) else: qual_clause = "" if self.withcheck: withcheck_clause = "\nwith check {}".format(self.withcheck) else: withcheck_clause = "" roleslist = ", ".join(self.roles) return RLS_POLICY_CREATE.format( name=self.quoted_name, table_name=self.quoted_full_table_name, permissiveness=self.permissiveness, commandtype_keyword=self.commandtype_keyword, roleslist=roleslist, qual_clause=qual_clause, withcheck_clause=withcheck_clause, ) @property def drop_statement(self): return "drop policy {} on {};".format( self.quoted_name, self.quoted_full_table_name ) def __eq__(self, other): equalities = ( self.name == self.name, self.schema == other.schema, self.permissiveness == other.permissiveness, self.commandtype == other.commandtype, self.permissive == other.permissive, self.roles == other.roles, self.qual == other.qual, self.withcheck == other.withcheck, ) return all(equalities) class PostgreSQL(DBInspector): def __init__(self, c, include_internal=False): def processed(q): if not include_internal: q = q.replace("-- SKIP_INTERNAL", "") if c.dialect.server_version_info[0] == 10: q = q.replace("-- PG_10", "") else: q = q.replace("-- PG_!10", "") q = text(q) return q self.ALL_RELATIONS_QUERY = processed(ALL_RELATIONS_QUERY) self.INDEXES_QUERY = processed(INDEXES_QUERY) self.SEQUENCES_QUERY = processed(SEQUENCES_QUERY) self.CONSTRAINTS_QUERY = processed(CONSTRAINTS_QUERY) self.FUNCTIONS_QUERY = processed(FUNCTIONS_QUERY) self.EXTENSIONS_QUERY = processed(EXTENSIONS_QUERY) self.ENUMS_QUERY = processed(ENUMS_QUERY) self.DEPS_QUERY = processed(DEPS_QUERY) self.SCHEMAS_QUERY = processed(SCHEMAS_QUERY) self.PRIVILEGES_QUERY = processed(PRIVILEGES_QUERY) self.TRIGGERS_QUERY = processed(TRIGGERS_QUERY) self.COLLATIONS_QUERY = processed(COLLATIONS_QUERY) self.RLSPOLICIES_QUERY = processed(RLSPOLICIES_QUERY) super(PostgreSQL, self).__init__(c, include_internal) def load_all(self): self.load_schemas() self.load_all_relations() self.load_functions() self.selectables = od() self.selectables.update(self.relations) self.selectables.update(self.functions) self.load_deps() self.load_deps_all() self.load_privileges() self.load_triggers() self.load_collations() self.load_rlspolicies() def load_schemas(self): q = self.c.execute(self.SCHEMAS_QUERY) schemas = [InspectedSchema(schema=each.schema) for each in q] self.schemas = od((schema.schema, schema) for schema in schemas) def load_rlspolicies(self): q = self.c.execute(self.RLSPOLICIES_QUERY) rlspolicies = [ InspectedRowPolicy( name=p.name, schema=p.schema, table_name=p.table_name, commandtype=p.commandtype, permissive=p.permissive, roles=p.roles, qual=p.qual, withcheck=p.withcheck, ) for p in q ] self.rlspolicies = od((p.key, p) for p in rlspolicies) def load_collations(self): q = self.c.execute(self.COLLATIONS_QUERY) collations = [ InspectedCollation( schema=i.schema, name=i.name, provider=i.provider, encoding=i.encoding, lc_collate=i.lc_collate, lc_ctype=i.lc_ctype, version=i.version, ) for i in q ] self.collations = od((i.quoted_full_name, i) for i in collations) def load_privileges(self): q = self.c.execute(self.PRIVILEGES_QUERY) privileges = [ InspectedPrivilege( object_type=i.object_type, schema=i.schema, name=i.name, privilege=i.privilege, target_user=i.user, ) for i in q ] self.privileges = od((i.key, i) for i in privileges) def load_deps(self): q = self.c.execute(self.DEPS_QUERY) for dep in q: x = quoted_identifier(dep.name, dep.schema) x_dependent_on = quoted_identifier( dep.name_dependent_on, dep.schema_dependent_on ) self.selectables[x].dependent_on.append(x_dependent_on) self.selectables[x].dependent_on.sort() self.selectables[x_dependent_on].dependents.append(x) self.selectables[x_dependent_on].dependents.sort() def load_deps_all(self): def get_related_for_item(item, att): related = [self.selectables[_] for _ in getattr(item, att)] return [item.signature] + [ _ for d in related for _ in get_related_for_item(d, att) ] for k, x in self.selectables.items(): d_all = get_related_for_item(x, "dependent_on")[1:] d_all.sort() x.dependent_on_all = d_all d_all = get_related_for_item(x, "dependents")[1:] d_all.sort() x.dependents_all = d_all @property def partitioned_tables(self): return od((k, v) for k, v in self.tables.items() if v.is_partitioned) @property def alterable_tables(self): # ordinary tables and parent tables return od((k, v) for k, v in self.tables.items() if v.is_alterable) @property def data_tables(self): # ordinary tables and child tables return od((k, v) for k, v in self.tables.items() if v.contains_data) @property def child_tables(self): return od((k, v) for k, v in self.tables.items() if v.is_child_table) @property def tables_using_partitioning(self): return od((k, v) for k, v in self.tables.items() if v.uses_partitioning) @property def tables_not_using_partitioning(self): return od((k, v) for k, v in self.tables.items() if not v.uses_partitioning) def load_all_relations(self): self.tables = od() self.views = od() self.materialized_views = od() self.composite_types = od() q = self.c.execute(self.ENUMS_QUERY) enumlist = [ InspectedEnum(name=i.name, schema=i.schema, elements=i.elements) for i in q ] self.enums = od((i.quoted_full_name, i) for i in enumlist) q = self.c.execute(self.ALL_RELATIONS_QUERY) for _, g in groupby(q, lambda x: (x.relationtype, x.schema, x.name)): clist = list(g) f = clist[0] def get_enum(name, schema): if not name and not schema: return None quoted_full_name = "{}.{}".format( quoted_identifier(schema), quoted_identifier(name) ) return self.enums[quoted_full_name] columns = [ ColumnInfo( name=c.attname, dbtype=c.datatype, dbtypestr=c.datatypestring, pytype=self.to_pytype(c.datatype), default=c.defaultdef, not_null=c.not_null, is_enum=c.is_enum, enum=get_enum(c.enum_name, c.enum_schema), collation=c.collation, ) for c in clist if c.position_number ] s = InspectedSelectable( name=f.name, schema=f.schema, columns=od((c.name, c) for c in columns), relationtype=f.relationtype, definition=f.definition, comment=f.comment, parent_table=f.parent_table, partition_def=f.partition_def, rowsecurity=f.rowsecurity, forcerowsecurity=f.forcerowsecurity, ) RELATIONTYPES = { "r": "tables", "v": "views", "m": "materialized_views", "c": "composite_types", "p": "tables", } att = getattr(self, RELATIONTYPES[f.relationtype]) att[s.quoted_full_name] = s self.relations = od() for x in (self.tables, self.views, self.materialized_views): self.relations.update(x) q = self.c.execute(self.INDEXES_QUERY) indexlist = [ InspectedIndex( name=i.name, schema=i.schema, definition=i.definition, table_name=i.table_name, key_columns=i.key_columns, key_options=i.key_options, num_att=i.num_att, is_unique=i.is_unique, is_pk=i.is_pk, is_exclusion=i.is_exclusion, is_immediate=i.is_immediate, is_clustered=i.is_clustered, key_collations=i.key_collations, key_expressions=i.key_expressions, partial_predicate=i.partial_predicate, ) for i in q ] self.indexes = od((i.quoted_full_name, i) for i in indexlist) q = self.c.execute(self.SEQUENCES_QUERY) sequencelist = [InspectedSequence(name=i.name, schema=i.schema) for i in q] self.sequences = od((i.quoted_full_name, i) for i in sequencelist) q = self.c.execute(self.CONSTRAINTS_QUERY) constraintlist = [ InspectedConstraint( name=i.name, schema=i.schema, constraint_type=i.constraint_type, table_name=i.table_name, definition=i.definition, index=i.index, ) for i in q ] self.constraints = od((i.quoted_full_name, i) for i in constraintlist) q = self.c.execute(self.EXTENSIONS_QUERY) extensionlist = [ InspectedExtension(name=i.name, schema=i.schema, version=i.version) for i in q ] # extension names are unique per-database rather than per-schema like other things (even though extensions are assigned to a particular schema) self.extensions = od((i.name, i) for i in extensionlist) # add indexes and constraints to each table for each in self.indexes.values(): t = each.quoted_full_table_name n = each.quoted_full_name self.relations[t].indexes[n] = each for each in self.constraints.values(): t = each.quoted_full_table_name n = each.quoted_full_name self.relations[t].constraints[n] = each def load_functions(self): self.functions = od() q = self.c.execute(self.FUNCTIONS_QUERY) for _, g in groupby(q, lambda x: (x.schema, x.name, x.identity_arguments)): clist = list(g) f = clist[0] outs = [c for c in clist if c.parameter_mode == "OUT"] columns = [ ColumnInfo( name=c.parameter_name, dbtype=c.data_type, pytype=self.to_pytype(c.data_type), ) for c in outs ] if outs: columns = [ ColumnInfo( name=c.parameter_name, dbtype=c.data_type, pytype=self.to_pytype(c.data_type), ) for c in outs ] else: columns = [ ColumnInfo( name=f.name, dbtype=f.data_type, pytype=self.to_pytype(f.returntype), default=f.parameter_default, ) ] plist = [ ColumnInfo( name=c.parameter_name, dbtype=c.data_type, pytype=self.to_pytype(c.data_type), default=c.parameter_default, ) for c in clist if c.parameter_mode == "IN" ] s = InspectedFunction( schema=f.schema, name=f.name, columns=od((c.name, c) for c in columns), inputs=plist, identity_arguments=f.identity_arguments, result_string=f.result_string, language=f.language, definition=f.definition, strictness=f.strictness, security_type=f.security_type, volatility=f.volatility, full_definition=f.full_definition, comment=f.comment, ) identity_arguments = "({})".format(s.identity_arguments) self.functions[s.quoted_full_name + identity_arguments] = s def load_triggers(self): q = self.c.execute(self.TRIGGERS_QUERY) triggers = [ InspectedTrigger( i.name, i.schema, i.table_name, i.proc_schema, i.proc_name, i.enabled, i.full_definition, ) for i in q ] # type: list[InspectedTrigger] self.triggers = od((t.name, t) for t in triggers) def one_schema(self, schema): props = "schemas relations tables views functions selectables sequences constraints indexes enums extensions privileges collations" for prop in props.split(): att = getattr(self, prop) filtered = {k: v for k, v in att.items() if v.schema == schema} setattr(self, prop, filtered) def __eq__(self, other): """ :type other: PostgreSQL :rtype: bool """ return ( type(self) == type(other) and self.schemas == other.schemas and self.relations == other.relations and self.sequences == other.sequences and self.enums == other.enums and self.constraints == other.constraints and self.extensions == other.extensions and self.functions == other.functions and self.triggers == other.triggers and self.collations == other.collations and self.rlspolicies == other.rlspolicies ) PK!lD<<#schemainspect/pg/sql/collations.sqlselect collname as name, n.nspname as schema, case collprovider when 'd' then 'database default' when 'i' then 'icu' when 'c' then 'libc' end as provider, collencoding as encoding, collcollate as lc_collate, collctype as lc_ctype, collversion as version from pg_collation c INNER JOIN pg_namespace n ON n.oid=c.collnamespace -- SKIP_INTERNAL where nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%' order by 2, 1 PK!'|$schemainspect/pg/sql/constraints.sqlwith extension_oids as ( select objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass ), indexes as ( select schemaname as schema, tablename as table_name, indexname as name, indexdef as definition, indexdef as create_statement FROM pg_indexes -- SKIP_INTERNAL where schemaname not in ('pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and schemaname not like 'pg_temp_%' and schemaname not like 'pg_toast_temp_%' order by schemaname, tablename, indexname ) select nspname as schema, conname as name, relname as table_name, pg_get_constraintdef(pg_constraint.oid) as definition, tc.constraint_type as constraint_type, i.name as index, e.objid as extension_oid from pg_constraint INNER JOIN pg_class ON conrelid=pg_class.oid INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace inner join information_schema.table_constraints tc on nspname = tc.constraint_schema and conname = tc.constraint_name and relname = tc.table_name left outer join indexes i on nspname = i.schema and conname = i.name and relname = i.table_name left outer join extension_oids e on pg_class.oid = e.objid where true -- SKIP_INTERNAL and nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast', 'pg_temp_1', 'pg_toast_temp_1') -- SKIP_INTERNAL and e.objid is null order by 1, 3, 2; PK!MS,,schemainspect/pg/sql/deps.sql with things1 as ( select oid as objid, pronamespace as namespace, proname || '(' || pg_get_function_identity_arguments(oid) || ')' as name, 'f' as kind from pg_proc union select oid, relnamespace as namespace, relname as name, relkind as kind from pg_class ), extension_objids as ( select objid as extension_objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass ), things as ( select objid, kind, n.nspname as schema, name from things1 t inner join pg_namespace n on t.namespace = n.oid left outer join extension_objids on t.objid = extension_objids.extension_objid where kind in ('r', 'v', 'm', 'c', 'f') and nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%' and extension_objids.extension_objid is null ), combined as ( select distinct t.objid, t.schema, t.name, things_dependent_on.objid as objid_dependent_on, things_dependent_on.schema as schema_dependent_on, things_dependent_on.name as name_dependent_on FROM pg_depend d inner join things things_dependent_on on d.refobjid = things_dependent_on.objid inner join pg_rewrite rw on d.objid = rw.oid and things_dependent_on.objid != rw.ev_class inner join things t on rw.ev_class = t.objid where d.deptype in ('n') and rw.rulename = '_RETURN' ) select * from combined; PK!jAAschemainspect/pg/sql/enums.sqlwith extension_oids as ( select objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass ) SELECT n.nspname as "schema", substr(pg_catalog.format_type(t.oid, NULL), strpos(pg_catalog.format_type(t.oid, NULL), '.') + 1) AS "name", ARRAY( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE e.enumtypid = t.oid ORDER BY e.enumsortorder ) as elements FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace left outer join extension_oids e on t.oid = e.objid WHERE t.typcategory = 'E' and e.objid is null -- SKIP_INTERNAL and n.nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%' ORDER BY 1, 2; PK!>#schemainspect/pg/sql/extensions.sqlselect nspname as schema, extname as name, extversion as version, e.oid as oid from pg_extension e INNER JOIN pg_namespace ON pg_namespace.oid=e.extnamespace order by schema, name; PK!s s "schemainspect/pg/sql/functions.sqlwith r1 as ( SELECT r.routine_name as name, r.routine_schema as schema, r.specific_name as specific_name, r.data_type, r.external_language, r.routine_definition as definition FROM information_schema.routines r -- SKIP_INTERNAL where r.external_language not in ('C', 'INTERNAL') -- SKIP_INTERNAL and r.routine_schema not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and r.routine_schema not like 'pg_temp_%' and r.routine_schema not like 'pg_toast_temp_%' order by r.specific_name ), pgproc as ( select nspname as schema, proname as name, p.oid as oid, case proisstrict when true then 'RETURNS NULL ON NULL INPUT' else 'CALLED ON NULL INPUT' end as strictness, case prosecdef when true then 'SECURITY DEFINER' else 'SECURITY INVOKER' end as security_type, case provolatile when 'i' then 'IMMUTABLE' when 's' then 'STABLE' when 'v' then 'VOLATILE' else null end as volatility from pg_proc p INNER JOIN pg_namespace n ON n.oid=p.pronamespace -- SKIP_INTERNAL where nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%' ), extension_oids as ( select objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass and d.classid = 'pg_proc'::regclass ), r as ( select r1.*, pp.volatility, pp.strictness, pp.security_type, pp.oid, e.objid as extension_oid from r1 left outer join pgproc pp on r1.schema = pp.schema and r1.specific_name = pp.name || '_' || pp.oid left outer join extension_oids e on pp.oid = e.objid -- SKIP_INTERNAL where e.objid is null ), pre as ( SELECT r.schema as schema, r.name as name, r.data_type as returntype, p.parameter_name as parameter_name, p.data_type as data_type, p.parameter_mode as parameter_mode, p.parameter_default as parameter_default, p.ordinal_position as position_number, r.definition as definition, pg_get_functiondef(oid) as full_definition, r.external_language as language, r.strictness as strictness, r.security_type as security_type, r.volatility as volatility, r.oid as oid, r.extension_oid as extension_oid, pg_get_function_result(oid) as result_string, pg_get_function_identity_arguments(oid) as identity_arguments, pg_catalog.obj_description(r.oid) as comment FROM r LEFT JOIN information_schema.parameters p ON r.specific_name=p.specific_name order by name, parameter_mode, ordinal_position, parameter_name ) select * from pre order by schema, name, parameter_mode, position_number, parameter_name; PK!ZG schemainspect/pg/sql/indexes.sqlwith extension_oids as ( select objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass ) SELECT n.nspname AS schema, c.relname AS table_name, i.relname AS name, i.oid as oid, e.objid as extension_oid, pg_get_indexdef(i.oid) AS definition, (select string_agg(attname, ' ' order by attname) from pg_attribute where attnum = any(string_to_array(x.indkey::text, ' ')::int[]) and attrelid = x.indrelid) key_columns, indoption key_options, indnatts num_att, indisunique is_unique, indisprimary is_pk, indisexclusion is_exclusion, indimmediate is_immediate, indisclustered is_clustered, indcollation key_collations, indexprs key_expressions, indpred partial_predicate FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class i ON i.oid = x.indexrelid LEFT JOIN pg_namespace n ON n.oid = c.relnamespace left join extension_oids e on c.oid = e.objid or i.oid = e.objid WHERE c.relkind in ('r', 'm', 'p') AND i.relkind in ('i', 'I') -- SKIP_INTERNAL and nspname not in ('pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%' -- SKIP_INTERNAL and e.objid is null order by 1, 2, 3; PK!0#schemainspect/pg/sql/privileges.sqlselect table_schema as schema, table_name as name, 'table' as object_type, grantee as user, privilege_type as privilege from information_schema.role_table_grants where grantee != ( select tableowner from pg_tables where schemaname = table_schema and tablename = table_name ) -- SKIP_INTERNAL and table_schema not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and table_schema not like 'pg_temp_%' and table_schema not like 'pg_toast_temp_%' order by schema, name, user; PK!(LVV"schemainspect/pg/sql/relations.sqlwith extension_oids as ( select objid from pg_depend d WHERE d.refclassid = 'pg_extension'::regclass ), enums as ( SELECT t.oid as enum_oid, n.nspname as "schema", pg_catalog.format_type(t.oid, NULL) AS "name" FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace left outer join extension_oids e on t.oid = e.objid WHERE t.typcategory = 'E' and e.objid is null -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%' AND pg_catalog.pg_type_is_visible(t.oid) ORDER BY 1, 2 ), r as ( select c.relname as name, n.nspname as schema, c.relkind as relationtype, c.oid as oid, case when c.relkind in ('m', 'v') then pg_get_viewdef(c.oid) else null end as definition, case when c.relpartbound is not null then (SELECT '"' || nmsp_parent.nspname || '"."' || parent.relname || '"' as parent FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace where child.oid = c.oid ) end as parent_table, case when c.relpartbound is not null then pg_get_expr(c.relpartbound, c.oid, true) when c.relhassubclass is not null then pg_catalog.pg_get_partkeydef(c.oid) end as partition_def, c.relrowsecurity::boolean as rowsecurity, c.relforcerowsecurity::boolean as forcerowsecurity from pg_catalog.pg_class c inner join pg_catalog.pg_namespace n ON n.oid = c.relnamespace left outer join extension_oids e on c.oid = e.objid where c.relkind in ('r', 'v', 'm', 'c', 'p') -- SKIP_INTERNAL and e.objid is null -- SKIP_INTERNAL and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and n.nspname not like 'pg_temp_%' and n.nspname not like 'pg_toast_temp_%' ) select r.relationtype, r.schema, r.name, r.definition as definition, a.attnum as position_number, a.attname as attname, a.attnotnull as not_null, a.atttypid::regtype AS datatype, (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS collation, pg_get_expr(ad.adbin, ad.adrelid) as defaultdef, r.oid as oid, format_type(atttypid, atttypmod) AS datatypestring, e.enum_oid is not null as is_enum, e.name as enum_name, e.schema as enum_schema, pg_catalog.obj_description(r.oid) as comment, r.parent_table, r.partition_def, r.rowsecurity, r.forcerowsecurity FROM r left join pg_catalog.pg_attribute a on r.oid = a.attrelid and a.attnum > 0 left join pg_catalog.pg_attrdef ad on a.attrelid = ad.adrelid and a.attnum = ad.adnum left join enums e on a.atttypid = e.enum_oid where a.attisdropped is not true -- SKIP_INTERNAL and r.schema not in ('pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and r.schema not like 'pg_temp_%' and r.schema not like 'pg_toast_temp_%' order by relationtype, r.schema, r.name, position_number; PK!ښ$schemainspect/pg/sql/rlspolicies.sqlselect p.polname as name, n.nspname as schema, c.relname as table_name, p.polcmd as commandtype, p.polpermissive as permissive, (select array_agg(pg_get_userbyid(o)) from unnest(p.polroles) as unn(o)) as roles, p.polqual as qual, pg_get_expr(p.polqual, p.polrelid) as qual, pg_get_expr(p.polwithcheck, p.polrelid) as withcheck from pg_policy p join pg_class c ON c.oid = p.polrelid JOIN pg_namespace n ON n.oid = c.relnamespace order by 1 PK!`Y8   schemainspect/pg/sql/schemas.sqlselect nspname as schema from pg_catalog.pg_namespace -- SKIP_INTERNAL where nspname not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and nspname not like 'pg_temp_%' and nspname not like 'pg_toast_temp_%' order by 1; PK!IHH"schemainspect/pg/sql/sequences.sqlselect sequence_schema as schema, sequence_name as name from information_schema.sequences -- SKIP_INTERNAL where sequence_schema not in ('pg_internal', 'pg_catalog', 'information_schema', 'pg_toast') -- SKIP_INTERNAL and sequence_schema not like 'pg_temp_%' and sequence_schema not like 'pg_toast_temp_%' order by 1, 2; PK!N~!schemainspect/pg/sql/triggers.sqlselect tg.tgname "name", nsp.nspname "schema", cls.relname table_name, pg_get_triggerdef(tg.oid) full_definition, proc.proname proc_name, nspp.nspname proc_schema, tg.tgenabled enabled from pg_trigger tg join pg_class cls on cls.oid = tg.tgrelid join pg_namespace nsp on nsp.oid = cls.relnamespace join pg_proc proc on proc.oid = tg.tgfoid join pg_namespace nspp on nspp.oid = proc.pronamespace where not tg.tgisinternal order by schema, name; PK!a.schemainspect-0.1.1551953719.dist-info/LICENSEThis is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to PK!H|n-WY,schemainspect-0.1.1551953719.dist-info/WHEEL A н#Z;/" bFF]xzwK;<*mTֻ0*Ri.4Vm0[H, JPK!HNuo /schemainspect-0.1.1551953719.dist-info/METADATAU[o6~ׯ8KH^eA 4X]r$JfB IQ>RNb/6&w瞕̳OnjJ^5|JX /|S9x}&hnJQVPJ[:ז_1!S%eG/`qV{㦓I-ͳB7yn&DJ~~uέ/Ag2ZYH*_Y>'gV+P'=np^[4B4cnK@uj[pKmFlekaro[aKO࠸&L2t L^ }v0锻 heg@-ܾ)R%+ʃ+|}ߛ| &/Uo-eIȳ\rKw/0-;/dV[DPJy=1U6p5-%j*29}YU9M9QЕ4I$=ReuN Na'2t.lJ̡R꥛& ዆6H1JYt[LLe~#`!>Ao71Ky @V.2M["E"aem<ōgłt0Lv^`J݇6lKnyy 2&X=T-.SĀ[ D4 w$g F 9!qݘƺu Y8ƍAV#$vhQCߒ;@d;Qcg%Hr(ԓW>Cգعuxe(0rK/pBp3Tmq%PEJ6:dWbd , EC%R SEڃҦ4_6Q09#w\]\~t< zdԧ1G8âs#"&3aУC~${9Kxb\WhS0tm`Vq;z( }? K~FwPK!H e&-schemainspect-0.1.1551953719.dist-info/RECORDI:,ЗyX" 2($!BT?^VEs*hSp~~"p2OTZW| > HCrՕIZY~ӎ V~'7˧=pnۊ8~xpY@4tU_X_P|6}ܮ/#R ٚB-mF,˽'72q lLZYKP"Ūi@?m%!M0SQ-R^jX2 ;K,CpQ4'/Z_;<+ 5[x, :w!NZyG%B`~]V-b-\u<-"M=XWhrpwIħw5M:ލj?f0¼nad;J5C^Ώj30vʙqd>Kq{S4ϳO]I8bZni9"\ԎI{^>s}L4kl'F>X ݗW0=b]ҵ0ګqWvJjk+2"F!52c`UE9t@UVTcj[lx|DC`O0 nźn;DZjչ:S0}#hF~O?+l@lyǵj5崛JUB9%NƦc7'}o%Ј]`^(95Ҥsb 'IAC4 mߐ. rwGB /,,v'\a2H\r|KvP+| &QHzt(Ī˒BMc\Rv\ SF;/#3,Kp~Y,}0}ՖW33Q9jgeIx;-H ѡmn*m)hv7~ar|8\Xd0Ȗ;D=2Huhi9Tռ~/? VRE0s@;B\*[> BBo#8ƦMv$ ˚&=g%p4u6 ׿PK!2Bxxschemainspect/__init__.pyPK!1schemainspect/get.pyPK!#schemainspect/inspected.pyPK!ETqWWschemainspect/inspector.pyPK!6͌7 schemainspect/misc.pyPK!A7H>>C&schemainspect/pg/__init__.pyPK!b&schemainspect/pg/obj.pyPK!lD<<#schemainspect/pg/sql/collations.sqlPK!'|$pschemainspect/pg/sql/constraints.sqlPK!MS,,÷schemainspect/pg/sql/deps.sqlPK!jAA*schemainspect/pg/sql/enums.sqlPK!>#schemainspect/pg/sql/extensions.sqlPK!s s "schemainspect/pg/sql/functions.sqlPK!ZG hschemainspect/pg/sql/indexes.sqlPK!0#schemainspect/pg/sql/privileges.sqlPK!(LVV" schemainspect/pg/sql/relations.sqlPK!ښ$schemainspect/pg/sql/rlspolicies.sqlPK!`Y8   schemainspect/pg/sql/schemas.sqlPK!IHH"schemainspect/pg/sql/sequences.sqlPK!N~!schemainspect/pg/sql/triggers.sqlPK!a.schemainspect-0.1.1551953719.dist-info/LICENSEPK!H|n-WY,schemainspect-0.1.1551953719.dist-info/WHEELPK!HNuo /<schemainspect-0.1.1551953719.dist-info/METADATAPK!H e&-schemainspect-0.1.1551953719.dist-info/RECORDPKbX