diff options
author | Miroslav Šedivý <6774676+eumiro@users.noreply.github.com> | 2021-02-12 20:34:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-12 21:34:56 +0200 |
commit | 762fb4b8da98fdf6792e6c5586060ed37224f894 (patch) | |
tree | dca17819ff2a516988e2f74691dcef0554637464 | |
parent | 87ffae295edf4fb2a9c33c552b12f09921def29f (diff) |
Modernize code to Python 3.6+ (#1229)
1. `class A(object)` can be written as `class A:`
2. replace `dict([…])` and `set([…])` with `{…}`
3. use f-strings or compact `.format`
4. use `yield from` instead of `yield` in a `for` loop
5. import `mock` from `unittest`
6. expect `OSError` instead of `IOError` or `select` error
7. use Python3 defaults for file reading or `super()`
8. remove redundant parenthesis (keep those in tuples though)
9. shorten set intersection instead of creating lists
10. backslashes in strings do not have to be escaped if prepended with `r`
31 files changed, 368 insertions, 417 deletions
@@ -115,6 +115,7 @@ Contributors: * Jan Brun Rasmussen (janbrunrasmussen) * Kevin Marsh (kevinmarsh) * Eero Ruohola (ruohola) + * Miroslav Šedivý (eumiro) Creator: -------- diff --git a/pgcli/completion_refresher.py b/pgcli/completion_refresher.py index cf0879fd..3e847b09 100644 --- a/pgcli/completion_refresher.py +++ b/pgcli/completion_refresher.py @@ -6,7 +6,7 @@ from .pgcompleter import PGCompleter from .pgexecute import PGExecute -class CompletionRefresher(object): +class CompletionRefresher: refreshers = OrderedDict() @@ -141,7 +141,7 @@ def refresh_casing(completer, executor): with open(casing_file, "w") as f: f.write(casing_prefs) if os.path.isfile(casing_file): - with open(casing_file, "r") as f: + with open(casing_file) as f: completer.extend_casing([line.strip() for line in f]) diff --git a/pgcli/magic.py b/pgcli/magic.py index f58f4150..c2b60aa0 100644 --- a/pgcli/magic.py +++ b/pgcli/magic.py @@ -43,7 +43,7 @@ def pgcli_line_magic(line): conn._pgcli = pgcli # For convenience, print the connection alias - print("Connected: {}".format(conn.name)) + print(f"Connected: {conn.name}") try: pgcli.run_cli() diff --git a/pgcli/main.py b/pgcli/main.py index b1468985..7ab973a2 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -122,7 +122,7 @@ class PgCliQuitError(Exception): pass -class PGCli(object): +class PGCli: default_prompt = "\\u@\\h:\\d> " max_len_prompt = 30 @@ -325,11 +325,11 @@ class PGCli(object): if pattern not in TabularOutputFormatter().supported_formats: raise ValueError() self.table_format = pattern - yield (None, None, None, "Changed table format to {}".format(pattern)) + yield (None, None, None, f"Changed table format to {pattern}") except ValueError: - msg = "Table format {} not recognized. Allowed formats:".format(pattern) + msg = f"Table format {pattern} not recognized. Allowed formats:" for table_type in TabularOutputFormatter().supported_formats: - msg += "\n\t{}".format(table_type) + msg += f"\n\t{table_type}" msg += "\nCurrently set to: %s" % self.table_format yield (None, None, None, msg) @@ -386,7 +386,7 @@ class PGCli(object): try: with open(os.path.expanduser(pattern), encoding="utf-8") as f: query = f.read() - except IOError as e: + except OSError as e: return [(None, None, None, str(e), "", False, True)] if self.destructive_warning and confirm_destructive_query(query) is False: @@ -407,7 +407,7 @@ class PGCli(object): if not os.path.isfile(filename): try: open(filename, "w").close() - except IOError as e: + except OSError as e: self.output_file = None message = str(e) + "\nFile output disabled" return [(None, None, None, message, "", False, True)] @@ -479,7 +479,7 @@ class PGCli(object): service_config, file = parse_service_info(service) if service_config is None: click.secho( - "service '%s' was not found in %s" % (service, file), err=True, fg="red" + f"service '{service}' was not found in {file}", err=True, fg="red" ) exit(1) self.connect( @@ -515,7 +515,7 @@ class PGCli(object): passwd = os.environ.get("PGPASSWORD", "") # Find password from store - key = "%s@%s" % (user, host) + key = f"{user}@{host}" keyring_error_message = dedent( """\ {} @@ -677,7 +677,7 @@ class PGCli(object): click.echo(text, file=f) click.echo("\n".join(output), file=f) click.echo("", file=f) # extra newline - except IOError as e: + except OSError as e: click.secho(str(e), err=True, fg="red") else: if output: @@ -753,11 +753,7 @@ class PGCli(object): while self.watch_command: try: query = self.execute_command(self.watch_command) - click.echo( - "Waiting for {0} seconds before repeating".format( - timing - ) - ) + click.echo(f"Waiting for {timing} seconds before repeating") sleep(timing) except KeyboardInterrupt: self.watch_command = None @@ -1049,7 +1045,7 @@ class PGCli(object): str(self.pgexecute.port) if self.pgexecute.port is not None else "5432", ) string = string.replace("\\i", str(self.pgexecute.pid) or "(none)") - string = string.replace("\\#", "#" if (self.pgexecute.superuser) else ">") + string = string.replace("\\#", "#" if self.pgexecute.superuser else ">") string = string.replace("\\n", "\n") return string @@ -1384,7 +1380,7 @@ def is_mutating(status): if not status: return False - mutating = set(["insert", "update", "delete"]) + mutating = {"insert", "update", "delete"} return status.split(None, 1)[0].lower() in mutating diff --git a/pgcli/packages/parseutils/meta.py b/pgcli/packages/parseutils/meta.py index 108c01a3..333cab55 100644 --- a/pgcli/packages/parseutils/meta.py +++ b/pgcli/packages/parseutils/meta.py @@ -50,7 +50,7 @@ def parse_defaults(defaults_string): yield current -class FunctionMetadata(object): +class FunctionMetadata: def __init__( self, schema_name, diff --git a/pgcli/packages/parseutils/tables.py b/pgcli/packages/parseutils/tables.py index 0ec3e693..aaa676cc 100644 --- a/pgcli/packages/parseutils/tables.py +++ b/pgcli/packages/parseutils/tables.py @@ -42,8 +42,7 @@ def extract_from_part(parsed, stop_at_punctuation=True): for item in parsed.tokens: if tbl_prefix_seen: if is_subselect(item): - for x in extract_from_part(item, stop_at_punctuation): - yield x + yield from extract_from_part(item, stop_at_punctuation) elif stop_at_punctuation and item.ttype is Punctuation: return # An incomplete nested select won't be recognized correctly as a diff --git a/pgcli/packages/prioritization.py b/pgcli/packages/prioritization.py index e92dcbb6..f5a9cb57 100644 --- a/pgcli/packages/prioritization.py +++ b/pgcli/packages/prioritization.py @@ -16,10 +16,10 @@ def _compile_regex(keyword): keywords = get_literals("keywords") -keyword_regexs = dict((kw, _compile_regex(kw)) for kw in keywords) +keyword_regexs = {kw: _compile_regex(kw) for kw in keywords} -class PrevalenceCounter(object): +class PrevalenceCounter: def __init__(self): self.keyword_counts = defaultdict(int) self.name_counts = defaultdict(int) diff --git a/pgcli/packages/sqlcompletion.py b/pgcli/packages/sqlcompletion.py index 6ef88595..63053018 100644 --- a/pgcli/packages/sqlcompletion.py +++ b/pgcli/packages/sqlcompletion.py @@ -47,7 +47,7 @@ Alias = namedtuple("Alias", ["aliases"]) Path = namedtuple("Path", []) -class SqlStatement(object): +class SqlStatement: def __init__(self, full_text, text_before_cursor): self.identifier = None self.word_before_cursor = word_before_cursor = last_word( diff --git a/pgcli/pgcompleter.py b/pgcli/pgcompleter.py index 9c95a01c..79be274e 100644 --- a/pgcli/pgcompleter.py +++ b/pgcli/pgcompleter.py @@ -83,7 +83,7 @@ class PGCompleter(Completer): reserved_words = set(get_literals("reserved")) def __init__(self, smart_completion=True, pgspecial=None, settings=None): - super(PGCompleter, self).__init__() + super().__init__() self.smart_completion = smart_completion self.pgspecial = pgspecial self.prioritizer = PrevalenceCounter() @@ -177,7 +177,7 @@ class PGCompleter(Completer): :return: """ # casing should be a dict {lowercasename:PreferredCasingName} - self.casing = dict((word.lower(), word) for word in words) + self.casing = {word.lower(): word for word in words} def extend_relations(self, data, kind): """extend metadata for tables or views. @@ -279,8 +279,8 @@ class PGCompleter(Completer): fk = ForeignKey( parentschema, parenttable, parcol, childschema, childtable, childcol ) - childcolmeta.foreignkeys.append((fk)) - parcolmeta.foreignkeys.append((fk)) + childcolmeta.foreignkeys.append(fk) + parcolmeta.foreignkeys.append(fk) def extend_datatypes(self, type_data): @@ -424,7 +424,7 @@ class PGCompleter(Completer): # the same priority as unquoted names. lexical_priority = ( tuple( - 0 if c in (" _") else -ord(c) + 0 if c in " _" else -ord(c) for c in self.unescape_name(item.lower()) ) + (1,) @@ -517,9 +517,9 @@ class PGCompleter(Completer): # require_last_table is used for 'tb11 JOIN tbl2 USING (...' which should # suggest only columns that appear in the last table and one more ltbl = tables[-1].ref - other_tbl_cols = set( + other_tbl_cols = { c.name for t, cs in scoped_cols.items() if t.ref != ltbl for c in cs - ) + } scoped_cols = { t: [col for col in cols if col.name in other_tbl_cols] for t, cols in scoped_cols.items() @@ -574,7 +574,7 @@ class PGCompleter(Completer): tbls - TableReference iterable of tables already in query """ tbl = self.case(tbl) - tbls = set(normalize_ref(t.ref) for t in tbls) + tbls = {normalize_ref(t.ref) for t in tbls} if self.generate_aliases: tbl = generate_alias(self.unescape_name(tbl)) if normalize_ref(tbl) not in tbls: @@ -589,10 +589,10 @@ class PGCompleter(Completer): tbls = suggestion.table_refs cols = self.populate_scoped_cols(tbls) # Set up some data structures for efficient access - qualified = dict((normalize_ref(t.ref), t.schema) for t in tbls) - ref_prio = dict((normalize_ref(t.ref), n) for n, t in enumerate(tbls)) - refs = set(normalize_ref(t.ref) for t in tbls) - other_tbls = set((t.schema, t.name) for t in list(cols)[:-1]) + qualified = {normalize_ref(t.ref): t.schema for t in tbls} + ref_prio = {normalize_ref(t.ref): n for n, t in enumerate(tbls)} + refs = {normalize_ref(t.ref) for t in tbls} + other_tbls = {(t.schema, t.name) for t in list(cols)[:-1]} joins = [] # Iterate over FKs in existing tables to find potential joins fks = ( @@ -667,7 +667,7 @@ class PGCompleter(Completer): return d # Tables that are closer to the cursor get higher prio - ref_prio = dict((tbl.ref, num) for num, tbl in enumerate(suggestion.table_refs)) + ref_prio = {tbl.ref: num for num, tbl in enumerate(suggestion.table_refs)} # Map (schema, table, col) to tables coldict = list_dict( ((t.schema, t.name, c.name), t) for t, c in cols if t.ref != lref @@ -721,9 +721,7 @@ class PGCompleter(Completer): # Function overloading means we way have multiple functions of the same # name at this point, so keep unique names only all_functions = self.populate_functions(suggestion.schema, filt) - funcs = set( - self._make_cand(f, alias, suggestion, arg_mode) for f in all_functions - ) + funcs = {self._make_cand(f, alias, suggestion, arg_mode) for f in all_functions} matches = self.find_matches(word_before_cursor, funcs, meta="function") @@ -953,7 +951,7 @@ class PGCompleter(Completer): :return: {TableReference:{colname:ColumnMetaData}} """ - ctes = dict((normalize_ref(t.name), t.columns) for t in local_tbls) + ctes = {normalize_ref(t.name): t.columns for t in local_tbls} columns = OrderedDict() meta = self.dbmetadata diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py index d34bf26d..5cba7845 100644 --- a/pgcli/pgexecute.py +++ b/pgcli/pgexecute.py @@ -49,7 +49,7 @@ def _wait_select(conn): conn.cancel() # the loop will be broken by a server error continue - except select.error as e: + except OSError as e: errno = e.args[0] if errno != 4: raise @@ -127,7 +127,7 @@ def register_hstore_typecaster(conn): pass -class PGExecute(object): +class PGExecute: # The boolean argument to the current_schemas function indicates whether # implicit schemas, e.g. pg_catalog @@ -485,7 +485,7 @@ class PGExecute(object): try: cur.execute(sql, (spec,)) except psycopg2.ProgrammingError: - raise RuntimeError("View {} does not exist.".format(spec)) + raise RuntimeError(f"View {spec} does not exist.") result = cur.fetchone() view_type = "MATERIALIZED" if result[2] == "m" else "" return template.format(*result + (view_type,)) @@ -501,7 +501,7 @@ class PGExecute(object): result = cur.fetchone() return result[0] except psycopg2.ProgrammingError: - raise RuntimeError("Function {} does not exist.".format(spec)) + raise RuntimeError(f"Function {spec} does not exist.") def schemata(self): """Returns a list of schema names in the database""" @@ -527,21 +527,18 @@ class PGExecute(object): sql = cur.mogrify(self.tables_query, [kinds]) _logger.debug("Tables Query. sql: %r", sql) cur.execute(sql) - for row in cur: - yield row + yield from cur def tables(self): """Yields (schema_name, table_name) tuples""" - for row in self._relations(kinds=["r", "p", "f"]): - yield row + yield from self._relations(kinds=["r", "p", "f"]) def views(self): """Yields (schema_name, view_name) tuples. Includes both views and and materialized views """ - for row in self._relations(kinds=["v", "m"]): - yield row + yield from self._relations(kinds=["v", "m"]) def _columns(self, kinds=("r", "p", "f", "v", "m")): """Get column metadata for tables and views @@ -599,16 +596,13 @@ class PGExecute(object): sql = cur.mogrify(columns_query, [kinds]) _logger.debug("Columns Query. sql: %r", sql) cur.execute(sql) - for row in cur: - yield row + yield from cur def table_columns(self): - for row in self._columns(kinds=["r", "p", "f"]): - yield row + yield from self._columns(kinds=["r", "p", "f"]) def view_columns(self): - for row in self._columns(kinds=["v", "m"]): - yield row + yield from self._columns(kinds=["v", "m"]) def databases(self): with self.conn.cursor() as cur: @@ -804,8 +798,7 @@ class PGExecute(object): """ _logger.debug("Datatypes Query. sql: %r", query) cur.execute(query) - for row in cur: - yield row + yield from cur def casing(self): """Yields the most common casing for names used in db functions""" diff --git a/requirements-dev.txt b/requirements-dev.txt index 80e8f437..84fa6bf7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,4 @@ pytest>=2.7.0 -mock>=1.0.1 tox>=1.9.2 behave>=1.2.4 pexpect==3.3 diff --git a/tests/features/db_utils.py b/tests/features/db_utils.py index f57bc3b9..6898394e 100644 --- a/tests/features/db_utils.py +++ b/tests/features/db_utils.py @@ -44,7 +44,7 @@ def create_cn(hostname, password, username, dbname, port): host=hostname, user=username, database=dbname, password=password, port=port ) - print("Created connection: {0}.".format(cn.dsn)) + print(f"Created connection: {cn.dsn}.") return cn @@ -75,4 +75,4 @@ def close_cn(cn=None): """ if cn: cn.close() - print("Closed connection: {0}.".format(cn.dsn)) + print(f"Closed connection: {cn.dsn}.") diff --git a/tests/features/environment.py b/tests/features/environment.py index 049c2f23..a8b3e26a 100644 --- a/tests/features/environment.py +++ b/tests/features/environment.py @@ -38,7 +38,7 @@ def before_all(context): vi = "_".join([str(x) for x in sys.version_info[:3]]) db_name = context.config.userdata.get("pg_test_db", "pgcli_behave_tests") - db_name_full = "{0}_{1}".format(db_name, vi) + db_name_full = f"{db_name}_{vi}" # Store get params from config. context.conf = { @@ -122,12 +122,12 @@ def before_all(context): def show_env_changes(env_old, env_new): """Print out all test-specific env values.""" print("--- os.environ changed values: ---") - all_keys = set(list(env_old.keys()) + list(env_new.keys())) + all_keys = env_old.keys() | env_new.keys() for k in sorted(all_keys): old_value = env_old.get(k, "") new_value = env_new.get(k, "") if new_value and old_value != new_value: - print('{}="{}"'.format(k, new_value)) + print(f'{k}="{new_value}"') print("-" * 20) @@ -173,13 +173,13 @@ def after_scenario(context, scenario): # Quit nicely. if not context.atprompt: dbname = context.currentdb - context.cli.expect_exact("{0}> ".format(dbname), timeout=15) + context.cli.expect_exact(f"{dbname}> ", timeout=15) context.cli.sendcontrol("c") context.cli.sendcontrol("d") try: context.cli.expect_exact(pexpect.EOF, timeout=15) except pexpect.TIMEOUT: - print("--- after_scenario {}: kill cli".format(scenario.name)) + print(f"--- after_scenario {scenario.name}: kill cli") context.cli.kill(signal.SIGKILL) if hasattr(context, "tmpfile_sql_help") and context.tmpfile_sql_help: context.tmpfile_sql_help.close() diff --git a/tests/features/fixture_utils.py b/tests/features/fixture_utils.py index 16f123a6..70b603d8 100644 --- a/tests/features/fixture_utils.py +++ b/tests/features/fixture_utils.py @@ -18,7 +18,7 @@ def read_fixture_files(): """Read all files inside fixture_data directory.""" current_dir = os.path.dirname(__file__) fixture_dir = os.path.join(current_dir, "fixture_data/") - print("reading fixture data: {}".format(fixture_dir)) + print(f"reading fixture data: {fixture_dir}") fixture_dict = {} for filename in os.listdir(fixture_dir): if filename not in [".", ".."]: diff --git a/tests/features/steps/basic_commands.py b/tests/features/steps/basic_commands.py index a1fca8b0..07e9ec17 100644 --- a/tests/features/steps/basic_commands.py +++ b/tests/features/steps/basic_commands.py @@ -66,19 +66,19 @@ def step_ctrl_d(context): """ # turn off pager before exiting context.cli.sendcontrol("c") - context.cli.sendline("\pset pager off") + context.cli.sendline(r"\pset pager off") wrappers.wait_prompt(context) context.cli.sendcontrol("d") context.cli.expect(pexpect.EOF, timeout=15) context.exit_sent = True -@when('we send "\?" command') +@when(r'we send "\?" command') def step_send_help(context): - """ + r""" Send \? to see help. """ - context.cli.sendline("\?") + context.cli.sendline(r"\?") @when("we send partial select command") @@ -97,9 +97,9 @@ def step_see_error_message(context): @when("we send source command") def step_send_source_command(context): context.tmpfile_sql_help = tempfile.NamedTemporaryFile(prefix="pgcli_") - context.tmpfile_sql_help.write(b"\?") + context.tmpfile_sql_help.write(br"\?") context.tmpfile_sql_help.flush() - context.cli.sendline("\i {0}".format(context.tmpfile_sql_help.name)) + context.cli.sendline(fr"\i {context.tmpfile_sql_help.name}") wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5) diff --git a/tests/features/steps/crud_database.py b/tests/features/steps/crud_database.py index 3fd8b7a1..3f5d0e71 100644 --- a/tests/features/steps/crud_database.py +++ b/tests/features/steps/crud_database.py @@ -14,7 +14,7 @@ def step_db_create(context): """ Send create database. """ - context.cli.sendline("create database {0};".format(context.conf["dbname_tmp"])) + context.cli.sendline("create database {};".format(context.conf["dbname_tmp"])) context.response = {"database_name": context.conf["dbname_tmp"]} @@ -24,7 +24,7 @@ def step_db_drop(context): """ Send drop database. """ - context.cli.sendline("drop database {0};".format(context.conf["dbname_tmp"])) + context.cli.sendline("drop database {};".format(context.conf["dbname_tmp"])) @when("we connect to test database") @@ -33,7 +33,7 @@ def step_db_connect_test(context): Send connect to database. """ db_name = context.conf["dbname"] - context.cli.sendline("\\connect {0}".format(db_name)) + context.cli.sendline(f"\\connect {db_name}") @when("we connect to dbserver") @@ -59,7 +59,7 @@ def step_see_prompt(context): Wait to see the prompt. """ db_name = getattr(context, "currentdb", context.conf["dbname"]) - wrappers.expect_exact(context, "{0}> ".format(db_name), timeout=5) + wrappers.expect_exact(context, f"{db_name}> ", timeout=5) context.atprompt = True diff --git a/tests/features/steps/expanded.py b/tests/features/steps/expanded.py index f34fcf04..265ea39b 100644 --- a/tests/features/steps/expanded.py +++ b/tests/features/steps/expanded.py @@ -31,7 +31,7 @@ def step_prepare_data(context): @when("we set expanded {mode}") def step_set_expanded(context, mode): """Set expanded to mode.""" - context.cli.sendline("\\" + "x {}".format(mode)) + context.cli.sendline("\\" + f"x {mode}") wrappers.expect_exact(context, "Expanded display is", timeout=2) wrappers.wait_prompt(context) diff --git a/tests/features/steps/iocommands.py b/tests/features/steps/iocommands.py index 613aeb29..a614490a 100644 --- a/tests/features/steps/iocommands.py +++ b/tests/features/steps/iocommands.py @@ -13,7 +13,7 @@ def step_edit_file(context): ) if os.path.exists(context.editor_file_name): os.remove(context.editor_file_name) - context.cli.sendline("\e {0}".format(os.path.basename(context.editor_file_name))) + context.cli.sendline(r"\e {}".format(os.path.basename(context.editor_file_name))) wrappers.expect_exact( context, 'Entering Ex mode. Type "visual" to go to Normal mode.', timeout=2 ) @@ -53,7 +53,7 @@ def step_tee_ouptut(context): ) if os.path.exists(context.tee_file_name): os.remove(context.tee_file_name) - context.cli.sendline("\o {0}".format(os.path.basename(context.tee_file_name))) + context.cli.sendline(r"\o {}".format(os.path.basename(context.tee_file_name))) wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5) wrappers.expect_exact(context, "Writing to file", timeout=5) wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5) @@ -67,7 +67,7 @@ def step_query_select_123456(context): @when("we stop teeing output") def step_notee_output(context): - context.cli.sendline("\o") + context.cli.sendline(r"\o") wrappers.expect_exact(context, "Time", timeout=5) diff --git a/tests/features/steps/wrappers.py b/tests/features/steps/wrappers.py index e0f5a20e..78d76881 100644 --- a/tests/features/steps/wrappers.py +++ b/tests/features/steps/wrappers.py @@ -57,7 +57,7 @@ def run_cli(context, run_args=None, prompt_check=True, currentdb=None): context.cli.logfile = context.logfile context.exit_sent = False context.currentdb = currentdb or context.conf["dbname"] - context.cli.sendline("\pset pager always") + context.cli.sendline(r"\pset pager always") if prompt_check: wait_prompt(context) diff --git a/tests/metadata.py b/tests/metadata.py index 2f89ea28..4ebcccd0 100644 --- a/tests/metadata.py +++ b/tests/metadata.py @@ -3,7 +3,7 @@ from itertools import product from pgcli.packages.parseutils.meta import FunctionMetadata, ForeignKey from prompt_toolkit.completion import Completion from prompt_toolkit.document import Document -from mock import Mock +from unittest.mock import Mock import pytest parametrize = pytest.mark.parametrize @@ -59,7 +59,7 @@ def wildcard_expansion(cols, pos=-1): return Completion(cols, start_position=pos, display_meta="columns", display="*") -class MetaData(object): +class MetaData: def __init__(self, metadata): self.metadata = metadata @@ -128,7 +128,7 @@ class MetaData(object): ] def schemas(self, pos=0): - schemas = set(sch for schs in self.metadata.values() for sch in schs) + schemas = {sch for schs in self.metadata.values() for sch in schs} return [schema(escape(s), pos=pos) for s in schemas] def functions_and_keywords(self, parent="public", pos=0): diff --git a/tests/parseutils/test_parseutils.py b/tests/parseutils/test_pars |