From a761974d1dc6adddda472361c3676b560de1ca48 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Tue, 21 Nov 2023 16:24:07 +0100 Subject: [PATCH] adds github ci workflow --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++ clisnips/stores/snippets.py | 6 ++-- clisnips/tui/widgets/field/field.py | 10 +++--- tests/database/offset_pager_test.py | 26 +++++++++----- tests/database/scroll_cursor_test.py | 35 ++++++++++--------- tests/database/scrolling_pager_test.py | 19 ++++++----- tests/layouts/table_test.py | 5 ++- tests/syntax/command/parser_test.py | 25 ++++++++------ tests/syntax/documentation/lexer_test.py | 40 +++++++++++----------- tests/syntax/documentation/parser_test.py | 17 ++++++---- tests/utils/path_completion_test.py | 32 +++++++++--------- typings/observ/__init__.pyi | 11 +++--- 12 files changed, 164 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4457ac8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: Build and test + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + name: Test py@${{matrix.python-version}} / ${{matrix.os}} + runs-on: ${{matrix.os}} + continue-on-error: false + strategy: + fail-fast: true + matrix: + os: + - ubuntu-latest + python-version: + - "3.11" + steps: + - uses: actions/checkout@v4 + # https://github.com/actions/setup-python/issues/659 + - name: Install poetry + run: pipx install poetry + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{matrix.python-version}} + cache: poetry + - name: Install dependencies + run: poetry install + - name: Lint + run: | + poetry run ruff format --check . + poetry run ruff check + - name: Test + run: poetry run pytest diff --git a/clisnips/stores/snippets.py b/clisnips/stores/snippets.py index 84ad2a2..ac0ea43 100644 --- a/clisnips/stores/snippets.py +++ b/clisnips/stores/snippets.py @@ -80,9 +80,9 @@ def watch( self, expr: Callable[[State], Watched], on_change: Callable[[Watched], Any], - sync: bool =False, - deep: bool =False, - immediate: bool =False, + sync: bool = False, + deep: bool = False, + immediate: bool = False, ): return watch( fn=lambda: expr(self._state), diff --git a/clisnips/tui/widgets/field/field.py b/clisnips/tui/widgets/field/field.py index 8ba6013..cbb2f98 100644 --- a/clisnips/tui/widgets/field/field.py +++ b/clisnips/tui/widgets/field/field.py @@ -21,10 +21,12 @@ class SimpleField(Field, urwid.Pile): def __init__(self, label: TextMarkup, entry: Entry): self._entry = entry urwid.connect_signal(self._entry, 'changed', lambda *w: self._emit('changed')) - super().__init__([ - urwid.Text(label), - self._entry, # type: ignore - ]) + super().__init__( + [ + urwid.Text(label), + self._entry, # type: ignore + ] + ) def get_value(self): return self._entry.get_value() diff --git a/tests/database/offset_pager_test.py b/tests/database/offset_pager_test.py index 6a8e8d2..abe6e50 100644 --- a/tests/database/offset_pager_test.py +++ b/tests/database/offset_pager_test.py @@ -10,7 +10,7 @@ ('test bar', 6), ('test baz', 3), ('test qux', 4), - ('test foobar', 8) + ('test foobar', 8), ] @@ -24,11 +24,13 @@ def connection(): for i in range(4): for value, ranking in FIXTURES: rowid += 1 - value = '%s #%s' % (value, rowid) + value = f'{value} #{rowid}' con.execute( - '''insert into paging_test(rowid, value, ranking) - values(?, ?, ?)''', - (rowid, value, ranking) + """ + insert into paging_test(rowid, value, ranking) + values(?, ?, ?) + """, + (rowid, value, ranking), ) yield con con.close() @@ -78,15 +80,21 @@ def test_complex_query(connection): 1: [1, 5, 6, 10, 11], 2: [15, 16, 20], } - pager.set_query(''' + pager.set_query( + """ SELECT i.docid, t.rowid, t.* FROM paging_test t JOIN paging_test_idx i ON i.docid = t.rowid WHERE paging_test_idx MATCH :term - ''', params) - pager.set_count_query(''' + """, + params, + ) + pager.set_count_query( + """ SELECT docid FROM paging_test_idx WHERE paging_test_idx MATCH :term - ''', params) + """, + params, + ) pager.execute() assert len(pager) == 2, 'Wrong number of pages.' # diff --git a/tests/database/scroll_cursor_test.py b/tests/database/scroll_cursor_test.py index 17d5d60..888b530 100644 --- a/tests/database/scroll_cursor_test.py +++ b/tests/database/scroll_cursor_test.py @@ -1,4 +1,3 @@ - from clisnips.database import ScrollDirection, SortOrder from clisnips.database.scrolling_pager import Cursor @@ -23,10 +22,12 @@ def test_default_columns(): def test_single_sort_column(): - cursor = Cursor.with_columns(( - ('foo', SortOrder.DESC), - ('id', SortOrder.ASC, True), - )) + cursor = Cursor.with_columns( + ( + ('foo', SortOrder.DESC), + ('id', SortOrder.ASC, True), + ) + ) assert cursor.unique_column == ('id', SortOrder.ASC) assert cursor.sort_columns == [('foo', SortOrder.DESC)] assert list(cursor.columns()) == [ @@ -48,11 +49,13 @@ def test_single_sort_column(): def test_multiple_sort_columns(): - cursor = Cursor.with_columns(( - ('foo', SortOrder.DESC), - ('id', SortOrder.ASC, True), - ('bar', SortOrder.ASC), - )) + cursor = Cursor.with_columns( + ( + ('foo', SortOrder.DESC), + ('id', SortOrder.ASC, True), + ('bar', SortOrder.ASC), + ) + ) assert cursor.unique_column == ('id', SortOrder.ASC) assert cursor.sort_columns == [('foo', SortOrder.DESC), ('bar', SortOrder.ASC)] assert list(cursor.columns()) == [ @@ -81,11 +84,13 @@ def test_multiple_sort_columns(): def test_update(): - cursor = Cursor.with_columns(( - ('foo', SortOrder.DESC), - ('bar', SortOrder.ASC), - ('id', SortOrder.ASC, True), - )) + cursor = Cursor.with_columns( + ( + ('foo', SortOrder.DESC), + ('bar', SortOrder.ASC), + ('id', SortOrder.ASC, True), + ) + ) empty = {'id': None, 'foo': None, 'bar': None} assert cursor.first == cursor.last == empty diff --git a/tests/database/scrolling_pager_test.py b/tests/database/scrolling_pager_test.py index 7f1c756..5a254c6 100644 --- a/tests/database/scrolling_pager_test.py +++ b/tests/database/scrolling_pager_test.py @@ -26,10 +26,7 @@ def connection(): for value, ranking in FIXTURES: rowid += 1 value = '%s #%s' % (value, rowid) - con.execute( - '''INSERT INTO paging_test(rowid, value, ranking) VALUES(?, ?, ?)''', - (rowid, value, ranking) - ) + con.execute("""INSERT INTO paging_test(rowid, value, ranking) VALUES(?, ?, ?)""", (rowid, value, ranking)) yield con con.close() @@ -85,15 +82,21 @@ def test_simple_query(connection): def test_complex_query(connection): pager = ScrollingPager(connection, 5) params = {'term': 'foo*'} - pager.set_query(''' + pager.set_query( + """ SELECT i.docid, t.rowid, t.* from paging_test t JOIN paging_test_idx i ON i.docid = t.rowid WHERE paging_test_idx MATCH :term - ''', params) - pager.set_count_query(''' + """, + params, + ) + pager.set_count_query( + """ SELECT docid FROM paging_test_idx WHERE paging_test_idx MATCH :term - ''', params) + """, + params, + ) pager.set_sort_columns([('ranking', SortOrder.DESC), ('rowid', SortOrder.ASC, True)]) expected = { 1: [5, 10, 15, 20, 1], diff --git a/tests/layouts/table_test.py b/tests/layouts/table_test.py index b41393c..f2f70ec 100644 --- a/tests/layouts/table_test.py +++ b/tests/layouts/table_test.py @@ -1,4 +1,3 @@ - from clisnips.tui.layouts.table import LayoutColumn, LayoutRow, TableLayout @@ -28,10 +27,10 @@ def test_layout(): {'a': 'one', 'b': 'two', 'c': 'three'}, {'a': 'four', 'b': 'five', 'c': 'six'}, ] - expected = ''' + expected = """ | one | two | three | | four | five | six | -''' +""" table.layout(rows, 0) result = [] for row in table: diff --git a/tests/syntax/command/parser_test.py b/tests/syntax/command/parser_test.py index 314985d..ee2372d 100644 --- a/tests/syntax/command/parser_test.py +++ b/tests/syntax/command/parser_test.py @@ -18,7 +18,7 @@ def test_simple_replacement_field(): expected = [ Text('i haz ', 0, 6), Field('one', 6, 11), - Text(' field', 11, 17) + Text(' field', 11, 17), ] assert cmd.nodes == expected @@ -31,7 +31,7 @@ def test_it_supports_flags(): Field('-1', 6, 10), Text(' ', 10, 11), Field('--two', 11, 18), - Text(' flags', 18, 24) + Text(' flags', 18, 24), ] assert cmd.nodes == expected @@ -44,7 +44,7 @@ def test_automatic_numbering(): Field('0', 6, 8), Text(' ', 8, 9), Field('1', 9, 11), - Text(' flags', 11, 17) + Text(' flags', 11, 17), ] assert cmd.nodes == expected @@ -55,7 +55,7 @@ def test_conversion(): expected = [ Text('i haz ', 0, 6), Field('one', 6, 13, '', 'r'), - Text(' field', 13, 19) + Text(' field', 13, 19), ] assert cmd.nodes == expected @@ -66,7 +66,7 @@ def test_format_spec(): expected = [ Text('i haz ', 0, 6), Field('0', 6, 13, '.1f', None), - Text(' field', 13, 19) + Text(' field', 13, 19), ] assert cmd.nodes == expected @@ -98,11 +98,15 @@ def test_field_getattr(): def test_command_apply(): raw = 'i haz {} {:.2f} fields' cmd = parse(raw) - output = list(cmd.apply({ - '0': 'zaroo', - '1': 1 / 3, # type: ignore (we're testing if it handles other types correctly) - 'foo': {'bar': 42} - })) + output = list( + cmd.apply( + { + '0': 'zaroo', + '1': 1 / 3, # type: ignore (we're testing if it handles other types correctly) + 'foo': {'bar': 42}, + } + ) + ) expected = [ (False, 'i haz '), (True, 'zaroo'), @@ -111,4 +115,3 @@ def test_command_apply(): (False, ' fields'), ] assert expected == output - diff --git a/tests/syntax/documentation/lexer_test.py b/tests/syntax/documentation/lexer_test.py index 8caf4f1..f61344d 100644 --- a/tests/syntax/documentation/lexer_test.py +++ b/tests/syntax/documentation/lexer_test.py @@ -11,7 +11,7 @@ def assert_token_list_equal(actual_tokens, expected_tokens): assert expected == actual, 'Expected token.{} to equal {!r}, got {!r}'.format( attr, expected.name if attr == 'type' else expected, - actual.name if attr == 'type' else actual + actual.name if attr == 'type' else actual, ) @@ -42,7 +42,7 @@ def test_param_only(): {'kind': Tokens.LEFT_BRACE}, {'kind': Tokens.IDENTIFIER, 'value': 'par2'}, {'kind': Tokens.RIGHT_BRACE}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -58,7 +58,7 @@ def test_flags(): {'kind': Tokens.LEFT_BRACE}, {'kind': Tokens.FLAG, 'value': '--some-flag'}, {'kind': Tokens.RIGHT_BRACE}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -73,7 +73,7 @@ def test_type_hint_only(): {'kind': Tokens.LEFT_PAREN}, {'kind': Tokens.IDENTIFIER, 'value': 'string'}, {'kind': Tokens.RIGHT_PAREN}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) # FIXME: test that invalid type_hint is skipped @@ -93,7 +93,7 @@ def test_value_hint_string_list(): {'kind': Tokens.DEFAULT_MARKER}, {'kind': Tokens.STRING, 'value': 'value2'}, {'kind': Tokens.RIGHT_BRACKET}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -114,7 +114,7 @@ def test_value_hint_digit_list(): {'kind': Tokens.COMMA}, {'kind': Tokens.FLOAT, 'value': '0.3'}, {'kind': Tokens.RIGHT_BRACKET}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -132,7 +132,7 @@ def test_value_hint_range(): {'kind': Tokens.COLON}, {'kind': Tokens.INTEGER, 'value': '5'}, {'kind': Tokens.RIGHT_BRACKET}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) # range with step @@ -149,7 +149,7 @@ def test_value_hint_range(): {'kind': Tokens.COLON}, {'kind': Tokens.INTEGER, 'value': '2'}, {'kind': Tokens.RIGHT_BRACKET}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) # range with step and default @@ -168,7 +168,7 @@ def test_value_hint_range(): {'kind': Tokens.DEFAULT_MARKER}, {'kind': Tokens.INTEGER, 'value': '5'}, {'kind': Tokens.RIGHT_BRACKET}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -181,14 +181,14 @@ def test_free_text_after_param(): {'kind': Tokens.IDENTIFIER, 'value': 'par1'}, {'kind': Tokens.RIGHT_BRACE}, {'kind': Tokens.TEXT}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) - text = '''{ par1 } Some free text (wow, [snafu]) + text = """{ par1 } Some free text (wow, [snafu]) {par2} (hint) Other text {par3} (hint) ["foo"] Yet another doctext - ''' + """ tokens = tokenize(text) expected = [ {'kind': Tokens.LEFT_BRACE}, @@ -212,34 +212,34 @@ def test_free_text_after_param(): {'kind': Tokens.STRING, 'value': 'foo'}, {'kind': Tokens.RIGHT_BRACKET}, {'kind': Tokens.TEXT}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) def test_code_block(): - text = ''' + text = """ ``` import foo, bar params['foo'] = '{bar}' -```''' +```""" tokens = tokenize(text) expected = [ {'kind': Tokens.TEXT}, {'kind': Tokens.CODE_FENCE}, {'kind': Tokens.TEXT}, {'kind': Tokens.CODE_FENCE}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) - text = '''{par1} Some text + text = """{par1} Some text ``` import foo, bar params['foo'] = '{bar}' ``` {par2} Other text. - ''' + """ tokens = tokenize(text) expected = [ {'kind': Tokens.LEFT_BRACE}, @@ -254,7 +254,7 @@ def test_code_block(): {'kind': Tokens.IDENTIFIER, 'value': 'par2'}, {'kind': Tokens.RIGHT_BRACE}, {'kind': Tokens.TEXT}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) @@ -275,6 +275,6 @@ def test_complex_code_block(): {'kind': Tokens.CODE_FENCE}, {'kind': Tokens.TEXT}, {'kind': Tokens.CODE_FENCE}, - {'kind': Tokens.EOF} + {'kind': Tokens.EOF}, ] assert_token_list_equal(tokens, expected) diff --git a/tests/syntax/documentation/parser_test.py b/tests/syntax/documentation/parser_test.py index e57cab0..1fab37f 100644 --- a/tests/syntax/documentation/parser_test.py +++ b/tests/syntax/documentation/parser_test.py @@ -95,7 +95,7 @@ def test_parse_value_list(): assert param.text == '' values = param.value_hint assert isinstance(values, ValueList) - assert values.values == ["foo", "bar", "baz"] + assert values.values == ['foo', 'bar', 'baz'] assert values.default == 1 @@ -142,17 +142,20 @@ def test_parse_value_range(): def test_parse_code_block(): - code_str = ''' + code_str = """ import os.path if fields['infile'] and not fields['outfile']: path, ext = os.path.splitext(fields['infile']) fields['outfile'] = path + '.mp4' -''' - text = ''' +""" + text = ( + """ {infile} (path) The input file {outfile} (path) The output file ```%s``` - ''' % code_str + """ + % code_str + ) doc = parse(text) # assert 'infile' in doc.parameters @@ -177,8 +180,8 @@ def test_parse_code_block(): _vars = { 'fields': { 'infile': '/foo/bar.wav', - 'outfile': '' - } + 'outfile': '', + }, } code.execute(_vars) assert _vars['fields']['outfile'] == '/foo/bar.mp4' diff --git a/tests/utils/path_completion_test.py b/tests/utils/path_completion_test.py index 6fdf00c..97ab9bd 100644 --- a/tests/utils/path_completion_test.py +++ b/tests/utils/path_completion_test.py @@ -12,7 +12,6 @@ class StubProvider(PathCompletionProvider): - def __init__(self, results): self._results = results @@ -21,7 +20,6 @@ def get_completions(self, path): class TestCompletionEntry: - def test_dirname_ends_with_slash(self): entry = PathCompletionEntry('foo', '/usr/share/foo', FA.IS_DIR) assert 'foo/' == entry.display_name @@ -61,15 +59,16 @@ def test_it_sorts_directories_first(self): class TestPathCompletion: - def test_simple_filename_completion(self): path = '/foo/bar/ba' - provider = StubProvider([ - PathCompletionEntry('baz', '/foo/bar/baz', FA.IS_FILE), - PathCompletionEntry('bazar', '/foo/bar/bazar', FA.IS_FILE), - PathCompletionEntry('baffle', '/foo/bar/baffle', FA.IS_FILE), - PathCompletionEntry('woot', '/foo/bar/woot', FA.IS_FILE), - ]) + provider = StubProvider( + [ + PathCompletionEntry('baz', '/foo/bar/baz', FA.IS_FILE), + PathCompletionEntry('bazar', '/foo/bar/bazar', FA.IS_FILE), + PathCompletionEntry('baffle', '/foo/bar/baffle', FA.IS_FILE), + PathCompletionEntry('woot', '/foo/bar/woot', FA.IS_FILE), + ] + ) completion = PathCompletion(provider) expected = [ PathCompletionEntry('baffle', '/foo/bar/baffle', FA.IS_FILE), @@ -116,11 +115,13 @@ def test_home_directory_completion(self): def test_no_directory_completion(self): path = 'ba' - provider = StubProvider([ - PathCompletionEntry('bar', '/home/user/bar/', FA.IS_DIR), - PathCompletionEntry('baz', '/home/user/baz', FA.IS_FILE), - PathCompletionEntry('qux', '/home/user/qux', FA.IS_FILE), - ]) + provider = StubProvider( + [ + PathCompletionEntry('bar', '/home/user/bar/', FA.IS_DIR), + PathCompletionEntry('baz', '/home/user/baz', FA.IS_FILE), + PathCompletionEntry('qux', '/home/user/qux', FA.IS_FILE), + ] + ) completion = PathCompletion(provider) expected = [ PathCompletionEntry('bar', '/home/user/bar/', FA.IS_DIR), @@ -132,7 +133,6 @@ def test_no_directory_completion(self): @pytest.mark.usefixtures('fs') class TestFileSystemCompletionProvider: - def test_simple_absolute_paths(self, fs): expected = [] for name in ('foo', 'bar', 'baz', 'qux'): @@ -169,7 +169,7 @@ def test_dot_relative_paths(self, fs): def test_dot_dot_relative_paths(self, fs): base_dir = '/usr/lib/X11' expected = [ - PathCompletionEntry('X11', base_dir, FA.IS_DIR) + PathCompletionEntry('X11', base_dir, FA.IS_DIR), ] fs.create_dir(base_dir) for name in ('foo', 'bar', 'baz', 'qux'): diff --git a/typings/observ/__init__.pyi b/typings/observ/__init__.pyi index 6c78b95..ea44059 100644 --- a/typings/observ/__init__.pyi +++ b/typings/observ/__init__.pyi @@ -4,22 +4,19 @@ from collections.abc import Callable from functools import partial from typing import Any, Generic, TypeVar, TypedDict +T = TypeVar('T') -T = TypeVar("T") class Proxy(Generic[T]): ... + def proxy(target: T, readonly=False, shallow=False) -> T: ... def to_raw(target: Proxy[T] | T) -> T: ... - def ref(target: T) -> Ref[T]: ... + class Ref(TypedDict, Generic[T]): value: T Watchable = Callable[[], T] | T -WatchCallback = ( - Callable[[], Any] - | Callable[[T], Any] - | Callable[[T, T], Any] -) +WatchCallback = Callable[[], Any] | Callable[[T], Any] | Callable[[T, T], Any] def watch( fn: Watchable[T],