diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 76e5f72..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,72 +0,0 @@ -version: 2.0 - -# -# Common components -# - -common_steps: &common_steps - working_directory: ~/repo - steps: - - checkout - - restore_cache: - key: v1-deps-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }} - - run: - name: Install Dependencies - command: pip install --user tox coveralls - - run: - name: Run Tests - command: ~/.local/bin/tox - - run: - name: Code Coverage - command: ~/.local/bin/coveralls - - save_cache: - paths: - - .tox - - ~/.cache/pip - - ~/.local - - ./eggs - key: v1-deps-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }} - -common_env: &common_env - TEST_DATABASE_BASE_URL: postgresql://root@127.0.0.1 - TEST_DATABASE_CI: circle_test - -common_postgres: &common_postgres - image: circleci/postgres:10-alpine - environment: - POSTGRES_DB: circle_test - POSTGRES_USER: root - POSTGRES_PASSWORD: "" - -# -# Jobs -# - -jobs: - py36: - <<: *common_steps - docker: - - image: circleci/python:3.6.6 - environment: - TOXENV: py36 - <<: *common_env - - <<: *common_postgres - py37: - <<: *common_steps - docker: - - image: circleci/python:3.7.0 - environment: - TOXENV: py37 - <<: *common_env - - <<: *common_postgres - -# -# Workflows -# - -workflows: - version: 2 - build_and_test: - jobs: - - py36 - - py37 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..fa7d4c9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +max-line-length = 120 +max-complexity = 10 +exclude = + psycopg2_pgevents/__init__.py, + tests diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml new file mode 100644 index 0000000..80515f9 --- /dev/null +++ b/.github/workflows/pipeline.yaml @@ -0,0 +1,142 @@ +name: Build, Lint, Test + +on: + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize + +jobs: + lint: + name: ๏ธโš’๏ธ & ๐Ÿ‘• + + strategy: + matrix: + python-version: [3.7, 3.8] + + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install poetry + uses: dschep/install-poetry-action@v1.3 + with: + create_virtualenvs: true + + - name: Cache Poetry virtualenv + uses: actions/cache@v2 + id: cache + with: + path: ~/.virtualenvs + key: poetry-${{ github.ref }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + poetry-${{ github.ref }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + + - name: Set Poetry config + run: | + poetry config virtualenvs.in-project false + poetry config virtualenvs.path ~/.virtualenvs + + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: poetry install + + - name: Run pre-commit + if: github.event_name == 'pull_request' + run: | + git fetch origin master:master + poetry run pre-commit run --from-ref master --to-ref HEAD + + test: + name: ๐Ÿงช + needs: lint + + strategy: + matrix: + python-version: [3.7, 3.8] + + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:10-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install poetry + uses: dschep/install-poetry-action@v1.3 + with: + create_virtualenvs: true + + - name: Cache Poetry virtualenv + uses: actions/cache@v2 + id: cache + with: + path: ~/.virtualenvs + key: poetry-${{ github.ref }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + poetry-${{ github.ref }}-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }} + + - name: Set Poetry config + run: | + poetry config virtualenvs.in-project false + poetry config virtualenvs.path ~/.virtualenvs + + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: poetry install + + - name: Unit Test + env: + TEST_DATABASE_BASE_URL: postgresql://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }} + run: poetry run python -m pytest --cov=psycopg2_pgevents --cov-branch tests/ + + - name: Submit Code Coverage + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: py-${{ matrix.python-version }} + COVERALLS_PARALLEL: true + run: poetry run coveralls + + test-finish: + name: ๐Ÿšช + needs: test + + runs-on: ubuntu-latest + container: python:3-slim + + steps: + - name: Finished + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + pip3 install coveralls + coveralls --finish diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..6839472 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,73 @@ +name: Package, Ship + +on: + push: + branches: + - master + +jobs: + package: + name: ๐Ÿ“ฆ & ๐Ÿšข + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: '0' + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - name: Get next package version + id: bumpversion + uses: anothrNick/github-tag-action@1.23.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: false + DRY_RUN: true + + - name: Install poetry + uses: dschep/install-poetry-action@v1.3 + with: + create_virtualenvs: true + + - name: Bump release version + run: poetry version ${{ steps.bumpversion.outputs.new_tag }} + + - name: Build package + run: poetry build + + - name: Publish package (Test PyPI) + run: | + poetry config repositories.testpypi https://test.pypi.org/legacy/ + poetry config pypi-token.testpypi ${{ secrets.TEST_PYPI_PASSWORD }} + poetry publish -r testpypi + + - name: Publish package (PyPI) + run: | + poetry config pypi-token.pypi ${{ secrets.PYPI_PASSWORD }} + poetry publish + + - name: Generate release message + id: release_message + run: | + # Get (squashed) commit summaries since last tag, convert to list, + # escape multi-lines into a format GitHub Actions respect, and set as output + export NOTES=`git log --pretty=format:'%s' $(git describe --tags --abbrev=0 HEAD^)..HEAD | tr '\n' '\0' | xargs -0 -I{} echo '- {}'` + export NOTES_ENCODED_NEWLINES="${NOTES//$'\n'/'%0A'}" + echo "::set-output name=message::$NOTES_ENCODED_NEWLINES" + + - name: Create release + uses: actions/create-release@v1.1.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.bumpversion.outputs.new_tag }} + release_name: ${{ steps.bumpversion.outputs.new_tag }} + body: ${{ steps.release_message.outputs.message}} + draft: false + prerelease: false diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..73081e1 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,4 @@ +[settings] +known_third_party = psycopg2,pytest,testfixtures +multi_line_output=3 +include_trailing_comma=True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3130eaa --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +--- +repos: + - repo: https://gitlab.com/smop/pre-commit-hooks + rev: v1.0.0 + hooks: + - id: check-poetry + - repo: https://github.com/asottile/seed-isort-config + rev: v2.2.0 + hooks: + - id: seed-isort-config + - repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black + - repo: https://github.com/pre-commit/mirrors-isort + rev: v5.0.9 + hooks: + - id: isort + - repo: https://github.com/pre-commit/pre-commit + rev: v2.6.0 + hooks: + - id: validate_manifest + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.1.0 + hooks: + - id: check-yaml + - id: check-toml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.3 + hooks: + - id: flake8 diff --git a/README.rst b/README.rst index ea94ddb..7ecfd18 100644 --- a/README.rst +++ b/README.rst @@ -4,8 +4,6 @@ psycopg2-pgevents .. image:: https://badge.fury.io/py/psycopg2-pgevents.svg :target: https://badge.fury.io/py/psycopg2-pgevents -.. image:: https://circleci.com/gh/shawalli/psycopg2-pgevents.svg?style=svg - :target: https://circleci.com/gh/shawalli/psycopg2-pgevents .. image:: https://coveralls.io/repos/github/shawalli/psycopg2-pgevents/badge.svg?branch=master :target: https://coveralls.io/github/shawalli/psycopg2-pgevents?branch=master .. image:: https://img.shields.io/badge/License-MIT-yellow.svg diff --git a/psycopg2_pgevents/__about__.py b/psycopg2_pgevents/__about__.py deleted file mode 100644 index d318868..0000000 --- a/psycopg2_pgevents/__about__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""This module contains information about this package.""" -__title__ = 'psycopg2-pgevents' -__summary__ = 'PostGreSQL LISTEN/NOTIFY functionality, via psycopg2.' -__uri__ = 'https://github.com/shawalli/psycopg2-pgevents' - -__version__ = '0.1.1' - -__author__ = 'Shawn Wallis' -__email__ = 'shawn.p.wallis@gmail.com' - -__license__ = 'MIT' -__copyright__ = 'Copyright 2018 {author}'.format(author=__author__) diff --git a/psycopg2_pgevents/__init__.py b/psycopg2_pgevents/__init__.py index a24ffdc..e650d8a 100644 --- a/psycopg2_pgevents/__init__.py +++ b/psycopg2_pgevents/__init__.py @@ -1,10 +1,17 @@ """This package provides the ability to listen for PostGreSQL table events at the database level.""" -from psycopg2_pgevents.__about__ import __author__, __copyright__, __email__, __license__, __summary__, \ - __title__, __uri__, __version__ - from psycopg2_pgevents.debug import log, set_debug -from psycopg2_pgevents.event import poll, register_event_channel, unregister_event_channel +from psycopg2_pgevents.event import ( + poll, + register_event_channel, + unregister_event_channel, +) from psycopg2_pgevents.sql import execute -from psycopg2_pgevents.trigger import install_trigger, install_trigger_function, trigger_function_installed, \ - trigger_installed, uninstall_trigger, uninstall_trigger_function +from psycopg2_pgevents.trigger import ( + install_trigger, + install_trigger_function, + trigger_function_installed, + trigger_installed, + uninstall_trigger, + uninstall_trigger_function, +) diff --git a/psycopg2_pgevents/debug.py b/psycopg2_pgevents/debug.py index d0793c0..4a72364 100644 --- a/psycopg2_pgevents/debug.py +++ b/psycopg2_pgevents/debug.py @@ -1,14 +1,14 @@ """This module provides functionality for debug logging within the package.""" -__all__ = ['log', 'set_debug'] +__all__ = ["log", "set_debug"] -from contextlib import contextmanager -from typing import Generator import logging import sys +from contextlib import contextmanager +from typing import Generator _DEBUG_ENABLED = False -_LOGGER_NAME = 'pgevents.debug' +_LOGGER_NAME = "pgevents.debug" def set_debug(enabled: bool): @@ -23,11 +23,11 @@ def set_debug(enabled: bool): global _DEBUG_ENABLED if not enabled: - log('Disabling debug output...', logger_name=_LOGGER_NAME) + log("Disabling debug output...", logger_name=_LOGGER_NAME) _DEBUG_ENABLED = False else: _DEBUG_ENABLED = True - log('Enabling debug output...', logger_name=_LOGGER_NAME) + log("Enabling debug output...", logger_name=_LOGGER_NAME) @contextmanager @@ -54,7 +54,7 @@ def _create_logger(name: str, level: int) -> Generator[logging.Logger, None, Non # Setup handler and add to logger handler = logging.StreamHandler(sys.stdout) - formatter = logging.Formatter('%(asctime)s %(levelname)-5s [%(name)s]: %(message)s') + formatter = logging.Formatter("%(asctime)s %(levelname)-5s [%(name)s]: %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) @@ -68,7 +68,7 @@ def _create_logger(name: str, level: int) -> Generator[logging.Logger, None, Non handler.close() -def log(message: str, *args: str, category: str='info', logger_name: str='pgevents'): +def log(message: str, *args: str, category: str = "info", logger_name: str = "pgevents"): """Log a message to the given logger. If debug has not been enabled, this method will not log a message. diff --git a/psycopg2_pgevents/event.py b/psycopg2_pgevents/event.py index 483c56c..baa4ad9 100644 --- a/psycopg2_pgevents/event.py +++ b/psycopg2_pgevents/event.py @@ -1,18 +1,18 @@ """This module provides functionality for managing and polling for events.""" -__all__ = ['Event', 'poll', 'register_event_channel', 'unregister_event_channel'] +__all__ = ["Event", "poll", "register_event_channel", "unregister_event_channel"] import json import select -from typing import Dict, Iterable +from typing import Iterable from uuid import UUID -from psycopg2_pgevents.sql import execute from psycopg2.extensions import connection from psycopg2_pgevents.debug import log +from psycopg2_pgevents.sql import execute -_LOGGER_NAME = 'pgevents.event' +_LOGGER_NAME = "pgevents.event" class Event: @@ -32,6 +32,7 @@ class Event: Row ID of event. This attribute is a string so that it can represent both regular id's and things like UUID's. """ + id: str type: str schema_name: str @@ -67,16 +68,12 @@ def __init__(self, id_: UUID, type_: str, schema_name: str, table_name: str, row self.row_id = row_id def __repr__(self): - return ' 'Event': + def fromjson(cls, json_string: str) -> "Event": """Create a new Event from a from a psycopg2-pgevent event JSON. Parameters @@ -91,13 +88,7 @@ def fromjson(cls, json_string: str) -> 'Event': """ obj = json.loads(json_string) - return cls( - UUID(obj['event_id']), - obj['event_type'], - obj['schema_name'], - obj['table_name'], - obj['row_id'] - ) + return cls(UUID(obj["event_id"]), obj["event_type"], obj["schema_name"], obj["table_name"], obj["row_id"]) def tojson(self) -> str: """Serialize an Event into JSON. @@ -108,13 +99,15 @@ def tojson(self) -> str: JSON-serialized Event. """ - return json.dumps({ - 'event_id': str(self.id), - 'event_type': self.type, - 'schema_name': self.schema_name, - 'table_name': self.table_name, - 'row_id': self.row_id - }) + return json.dumps( + { + "event_id": str(self.id), + "event_type": self.type, + "schema_name": self.schema_name, + "table_name": self.table_name, + "row_id": self.row_id, + } + ) def register_event_channel(connection: connection) -> None: @@ -130,7 +123,7 @@ def register_event_channel(connection: connection) -> None: None """ - log('Registering psycopg2-pgevents channel...', logger_name=_LOGGER_NAME) + log("Registering psycopg2-pgevents channel...", logger_name=_LOGGER_NAME) execute(connection, 'LISTEN "psycopg2_pgevents_channel";') @@ -147,11 +140,11 @@ def unregister_event_channel(connection: connection) -> None: None """ - log('Unregistering psycopg2-pgevents channel...', logger_name=_LOGGER_NAME) + log("Unregistering psycopg2-pgevents channel...", logger_name=_LOGGER_NAME) execute(connection, 'UNLISTEN "psycopg2_pgevents_channel";') -def poll(connection: connection, timeout: float=1.0) -> Iterable[Event]: +def poll(connection: connection, timeout: float = 1.0) -> Iterable[Event]: """Poll the connection for notification events. This method operates as an iterable. It will keep returning events until @@ -180,15 +173,15 @@ def poll(connection: connection, timeout: float=1.0) -> Iterable[Event]: """ if timeout > 0.0: - log('Polling for events (Blocking, {} seconds)...'.format(timeout), logger_name=_LOGGER_NAME) + log("Polling for events (Blocking, {} seconds)...".format(timeout), logger_name=_LOGGER_NAME) else: - log('Polling for events (Non-Blocking)...', logger_name=_LOGGER_NAME) + log("Polling for events (Non-Blocking)...", logger_name=_LOGGER_NAME) if select.select([connection], [], [], timeout) == ([], [], []): - log('...No events found', logger_name=_LOGGER_NAME) + log("...No events found", logger_name=_LOGGER_NAME) return else: - log('Events', logger_name=_LOGGER_NAME) - log('------', logger_name=_LOGGER_NAME) + log("Events", logger_name=_LOGGER_NAME) + log("------", logger_name=_LOGGER_NAME) connection.poll() while connection.notifies: event = connection.notifies.pop(0) diff --git a/psycopg2_pgevents/sql.py b/psycopg2_pgevents/sql.py index 0059884..a7dcd04 100644 --- a/psycopg2_pgevents/sql.py +++ b/psycopg2_pgevents/sql.py @@ -1,5 +1,5 @@ """This module provides functionality for interacting directly with the database.""" -__all__ = ['execute'] +__all__ = ["execute"] from typing import Dict, List, Optional, Tuple, Union @@ -9,27 +9,24 @@ from psycopg2_pgevents.debug import log -_LOGGER_NAME = 'pgevents.sql' +_LOGGER_NAME = "pgevents.sql" class Psycopg2Cursor(cursor): - def execute(self, query: str, args: Union[Dict, List, None]=None): - log('Query', logger_name=_LOGGER_NAME) - log('-----', logger_name=_LOGGER_NAME) + def execute(self, query: str, args: Union[Dict, List, None] = None): + log("Query", logger_name=_LOGGER_NAME) + log("-----", logger_name=_LOGGER_NAME) log(self.mogrify(query, args), logger_name=_LOGGER_NAME) try: super().execute(query, args) except Exception as e: - log('Exception', category='error', logger_name=_LOGGER_NAME) - log('---------', category='error', logger_name=_LOGGER_NAME) + log("Exception", category="error", logger_name=_LOGGER_NAME) + log("---------", category="error", logger_name=_LOGGER_NAME) log( - '{name}: {msg}'.format( - name=e.__class__.__name__, - msg=str(e) - ), - category='error', - logger_name=_LOGGER_NAME + "{name}: {msg}".format(name=e.__class__.__name__, msg=str(e)), + category="error", + logger_name=_LOGGER_NAME, ) raise @@ -70,19 +67,19 @@ def execute(connection: connection, statement: str) -> Optional[List[Tuple[str, response = cursor.fetchall() if not response: # Empty response list - log('', logger_name=_LOGGER_NAME) + log("", logger_name=_LOGGER_NAME) return None except ProgrammingError as e: - if e.args and e.args[0] == 'no results to fetch': + if e.args and e.args[0] == "no results to fetch": # No response available (i.e. no response given) - log('', logger_name=_LOGGER_NAME) + log("", logger_name=_LOGGER_NAME) return None # Some other programming error; re-raise raise e - log('Response', logger_name=_LOGGER_NAME) - log('--------', logger_name=_LOGGER_NAME) + log("Response", logger_name=_LOGGER_NAME) + log("--------", logger_name=_LOGGER_NAME) for line in response: log(str(line), logger_name=_LOGGER_NAME) diff --git a/psycopg2_pgevents/trigger.py b/psycopg2_pgevents/trigger.py index cc7260e..7808fa2 100644 --- a/psycopg2_pgevents/trigger.py +++ b/psycopg2_pgevents/trigger.py @@ -1,7 +1,12 @@ """This module provides functionality for managing triggers.""" -__all__ = ['install_trigger', 'install_trigger_function', 'trigger_function_installed', 'trigger_installed', - 'uninstall_trigger', 'uninstall_trigger_function' - ] +__all__ = [ + "install_trigger", + "install_trigger_function", + "trigger_function_installed", + "trigger_installed", + "uninstall_trigger", + "uninstall_trigger_function", +] from psycopg2 import ProgrammingError @@ -10,7 +15,7 @@ from psycopg2_pgevents.debug import log from psycopg2_pgevents.sql import execute -_LOGGER_NAME = 'pgevents.trigger' +_LOGGER_NAME = "pgevents.trigger" INSTALL_TRIGGER_FUNCTION_STATEMENT = """ @@ -95,7 +100,7 @@ def trigger_function_installed(connection: connection): """ installed = False - log('Checking if trigger function installed...', logger_name=_LOGGER_NAME) + log("Checking if trigger function installed...", logger_name=_LOGGER_NAME) try: execute(connection, "SELECT pg_get_functiondef('public.psycopg2_pgevents_create_event'::regproc);") @@ -104,7 +109,7 @@ def trigger_function_installed(connection: connection): if e.args: error_stdout = e.args[0].splitlines() error = error_stdout.pop(0) - if error.endswith('does not exist'): + if error.endswith("does not exist"): # Trigger function not installed pass else: @@ -114,12 +119,12 @@ def trigger_function_installed(connection: connection): # Some other exception; re-raise raise e - log('...{}installed'.format('' if installed else 'NOT '), logger_name=_LOGGER_NAME) + log("...{}installed".format("" if installed else "NOT "), logger_name=_LOGGER_NAME) return installed -def trigger_installed(connection: connection, table: str, schema: str='public'): +def trigger_installed(connection: connection, table: str, schema: str = "public"): """Test whether or not a psycopg2-pgevents trigger is installed for a table. Parameters @@ -139,23 +144,20 @@ def trigger_installed(connection: connection, table: str, schema: str='public'): """ installed = False - log('Checking if {}.{} trigger installed...'.format(schema, table), logger_name=_LOGGER_NAME) + log("Checking if {}.{} trigger installed...".format(schema, table), logger_name=_LOGGER_NAME) - statement = SELECT_TRIGGER_STATEMENT.format( - table=table, - schema=schema - ) + statement = SELECT_TRIGGER_STATEMENT.format(table=table, schema=schema) result = execute(connection, statement) if result: installed = True - log('...{}installed'.format('' if installed else 'NOT '), logger_name=_LOGGER_NAME) + log("...{}installed".format("" if installed else "NOT "), logger_name=_LOGGER_NAME) return installed -def install_trigger_function(connection: connection, overwrite: bool=False) -> None: +def install_trigger_function(connection: connection, overwrite: bool = False) -> None: """Install the psycopg2-pgevents trigger function against the database. Parameters @@ -177,14 +179,14 @@ def install_trigger_function(connection: connection, overwrite: bool=False) -> N prior_install = trigger_function_installed(connection) if not prior_install: - log('Installing trigger function...', logger_name=_LOGGER_NAME) + log("Installing trigger function...", logger_name=_LOGGER_NAME) execute(connection, INSTALL_TRIGGER_FUNCTION_STATEMENT) else: - log('Trigger function already installed; skipping...', logger_name=_LOGGER_NAME) + log("Trigger function already installed; skipping...", logger_name=_LOGGER_NAME) -def uninstall_trigger_function(connection: connection, force: bool=False) -> None: +def uninstall_trigger_function(connection: connection, force: bool = False) -> None: """Uninstall the psycopg2-pgevents trigger function from the database. Parameters @@ -201,17 +203,17 @@ def uninstall_trigger_function(connection: connection, force: bool=False) -> Non None """ - modifier = '' + modifier = "" if force: - modifier = 'CASCADE' + modifier = "CASCADE" - log('Uninstalling trigger function (cascade={})...'.format(force), logger_name=_LOGGER_NAME) + log("Uninstalling trigger function (cascade={})...".format(force), logger_name=_LOGGER_NAME) statement = UNINSTALL_TRIGGER_FUNCTION_STATEMENT.format(modifier=modifier) execute(connection, statement) -def install_trigger(connection: connection, table: str, schema: str='public', overwrite: bool=False) -> None: +def install_trigger(connection: connection, table: str, schema: str = "public", overwrite: bool = False) -> None: """Install a psycopg2-pgevents trigger against a table. Parameters @@ -237,18 +239,15 @@ def install_trigger(connection: connection, table: str, schema: str='public', ov prior_install = trigger_installed(connection, table, schema) if not prior_install: - log('Installing {}.{} trigger...'.format(schema, table), logger_name=_LOGGER_NAME) + log("Installing {}.{} trigger...".format(schema, table), logger_name=_LOGGER_NAME) - statement = INSTALL_TRIGGER_STATEMENT.format( - schema=schema, - table=table - ) + statement = INSTALL_TRIGGER_STATEMENT.format(schema=schema, table=table) execute(connection, statement) else: - log('{}.{} trigger already installed; skipping...'.format(schema, table), logger_name=_LOGGER_NAME) + log("{}.{} trigger already installed; skipping...".format(schema, table), logger_name=_LOGGER_NAME) -def uninstall_trigger(connection: connection, table: str, schema: str='public') -> None: +def uninstall_trigger(connection: connection, table: str, schema: str = "public") -> None: """Uninstall a psycopg2-pgevents trigger from a table. Parameters @@ -265,10 +264,7 @@ def uninstall_trigger(connection: connection, table: str, schema: str='public') None """ - log('Uninstalling {}.{} trigger...'.format(schema, table), logger_name=_LOGGER_NAME) + log("Uninstalling {}.{} trigger...".format(schema, table), logger_name=_LOGGER_NAME) - statement = UNINSTALL_TRIGGER_STATEMENT.format( - schema=schema, - table=table - ) + statement = UNINSTALL_TRIGGER_STATEMENT.format(schema=schema, table=table) execute(connection, statement) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..35e5a05 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[tool.poetry] +name = "psycopg2-pgevents" +version = "0.2.0" +description = "PostGreSQL LISTEN/NOTIFY functionality, via psycopg2" +readme = "README.rst" +homepage = "https://github.com/shawalli/psycopg2-pgevents" +repository = "https://github.com/shawalli/psycopg2-pgevents" +authors = ["Shawn Wallis "] +license = "MIT" +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Database", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", +] +include = [ + "LICENSE.txt", +] + +[tool.poetry.dependencies] +python = "^3.7" +psycopg2-binary = ">=2.7.5" +pytest = "^5.4.3" +pytest-cov = "^2.10.0" +coveralls = "^2.1.1" + +[tool.poetry.dev-dependencies] +pre-commit = "^2.6.0" +testfixtures = "^6.14.1" + +[tool.black] +line-length = 120 +target-version = ["py37", "py38"] +include = "\\.pyi?$" +exclude = """ +/( + \\.eggs + | \\.git + | \\.hg + | \\.mypy_cache + | \\.tox + | \\.venv + | _build + | buck-out + | build + | dist +)/ +""" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 68859ad..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[pep8] -max-line-length = 120 diff --git a/setup.py b/setup.py deleted file mode 100644 index 5a9f7bf..0000000 --- a/setup.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -""" -psycopg2-pgevents ------------------ - -Adds event notification support for PostGreSQL databases via psycopg2 and -NOTIFY/LISTEN. -""" -from pathlib import Path -from setuptools import setup - -about_file = Path(Path(__file__).parent, 'psycopg2_pgevents', '__about__.py') - -about = {} -with open(about_file) as f: - exec(f.read(), about) - -long_description = '' -with open('README.rst') as f: - long_description = f.read() - -setup( - name=about['__title__'], - version=about['__version__'], - description=about['__summary__'], - long_description=long_description, - long_description_content_type='text/x-rst', - author=about['__author__'], - author_email=about['__email__'], - url=about['__uri__'], - license=about['__license__'], - packages=['psycopg2_pgevents'], - zip_safe=False, - platforms='any', - install_requires=[ - 'psycopg2-binary>=2.7.5' - ], - tests_require=[ - 'pytest', - 'pytest-cov', - 'testfixtures' - ], - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Database', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities', - ] -) diff --git a/tests/conftest.py b/tests/conftest.py index 57ad346..552bb7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ - -from pathlib import Path from os import environ +from pathlib import Path from psycopg2 import connect from pytest import fixture @@ -8,13 +7,13 @@ from psycopg2_pgevents.debug import set_debug -DATABASE_BASE_URL = environ.get('TEST_DATABASE_BASE_URL', 'postgres://') -CI_DATABASE = environ.get('TEST_DATABASE_CI', 'postgres') +DATABASE_BASE_URL = environ.get("TEST_DATABASE_BASE_URL", "postgres://") +CI_DATABASE = environ.get("TEST_DATABASE_CI", "postgres") -CI_DATABASE_DSN = '/'.join([DATABASE_BASE_URL, CI_DATABASE]) -TEST_DATABASE_DSN = '/'.join([DATABASE_BASE_URL, 'test']) +CI_DATABASE_DSN = "/".join([DATABASE_BASE_URL, CI_DATABASE]) +TEST_DATABASE_DSN = "/".join([DATABASE_BASE_URL, "test"]) -DATABASE_SHAPE_SQL_FILE = Path(Path(__file__).parent, 'resources', 'database_shape.sql') +DATABASE_SHAPE_SQL_FILE = Path(Path(__file__).parent, "resources", "database_shape.sql") @fixture @@ -28,21 +27,21 @@ def log_capture(): @fixture def connection(): # Create fresh test database - _conn = connect(dsn=CI_DATABASE_DSN) + _conn = connect(dsn=CI_DATABASE_DSN, password="postgres") _conn.autocommit = True _curs = _conn.cursor() - _curs.execute('DROP DATABASE IF EXISTS test') - _curs.execute('CREATE DATABASE test') + _curs.execute("DROP DATABASE IF EXISTS test") + _curs.execute("CREATE DATABASE test") _conn.close() # Create test database connection - conn = connect(dsn=TEST_DATABASE_DSN) + conn = connect(dsn=TEST_DATABASE_DSN, password="postgres") conn.autocommit = True curs = conn.cursor() # Define test database shape - with open(DATABASE_SHAPE_SQL_FILE, 'r') as sql_file: + with open(DATABASE_SHAPE_SQL_FILE, "r") as sql_file: sql_contents = sql_file.read() curs.execute(sql_contents) @@ -56,7 +55,6 @@ def connection(): def client(): conn = connect(dsn=TEST_DATABASE_DSN) conn.autocommit = True - curs = conn.cursor() yield conn diff --git a/tests/test_debug.py b/tests/test_debug.py index 2190941..5f0912c 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -1,8 +1,7 @@ -import logging from pytest import raises from psycopg2_pgevents import debug -from psycopg2_pgevents.debug import set_debug, log +from psycopg2_pgevents.debug import log, set_debug class TestDebug: @@ -10,66 +9,59 @@ def test_set_debug_disabled(self): debug._DEBUG_ENABLED = True set_debug(False) - assert (debug._DEBUG_ENABLED == False) + assert not debug._DEBUG_ENABLED def test_set_debug_enabled(self): debug._DEBUG_ENABLED = False set_debug(True) - assert (debug._DEBUG_ENABLED == True) + assert debug._DEBUG_ENABLED def test_log_invalid_category(self, log_capture): with raises(ValueError): - log('foo', category='warningwarningwarning') + log("foo", category="warningwarningwarning") logs = log_capture.actual() - assert (len(logs) == 0) + assert len(logs) == 0 def test_log_debug_disabled(self, log_capture): set_debug(False) - log('foo') + log("foo") logs = log_capture.actual() # Only log should be the one notifying that logging is being disabled - assert (len(logs) == 1) + assert len(logs) == 1 def test_log_info(self, log_capture): - log('foo') + log("foo") logs = log_capture.actual() - assert (len(logs) == 1) - assert (('pgevents', 'INFO', 'foo') == logs.pop()) + assert len(logs) == 1 + assert ("pgevents", "INFO", "foo") == logs.pop() def test_log_error(self, log_capture): - log('foo', category='error') + log("foo", category="error") logs = log_capture.actual() - assert (len(logs) == 1) - assert (('pgevents', 'ERROR', 'foo') == logs.pop()) + assert len(logs) == 1 + assert ("pgevents", "ERROR", "foo") == logs.pop() def test_log_args(self, log_capture): - log('foo %s %s %d', 'bar', 'baz', 1) - log( - 'foo %(word1)s %(word2)s %(num)d', - { - 'word2': 'baz', - 'num': 1, - 'word1': 'bar' - } - ) + log("foo %s %s %d", "bar", "baz", 1) + log("foo %(word1)s %(word2)s %(num)d", {"word2": "baz", "num": 1, "word1": "bar"}) logs = log_capture.actual() - assert (len(logs) == 2) - assert (('pgevents', 'INFO', 'foo bar baz 1') == logs.pop(0)) - assert (('pgevents', 'INFO', 'foo bar baz 1') == logs.pop(0)) + assert len(logs) == 2 + assert ("pgevents", "INFO", "foo bar baz 1") == logs.pop(0) + assert ("pgevents", "INFO", "foo bar baz 1") == logs.pop(0) def test_log_custom_logger(self, log_capture): - log('foo', logger_name='test') + log("foo", logger_name="test") logs = log_capture.actual() - assert (len(logs) == 1) - assert (('test', 'INFO', 'foo') == logs.pop()) + assert len(logs) == 1 + assert ("test", "INFO", "foo") == logs.pop() diff --git a/tests/test_event.py b/tests/test_event.py index 88f5b1b..cbe9d6c 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -1,24 +1,23 @@ import json from uuid import UUID -from psycopg2 import ProgrammingError from pytest import fixture, mark +from psycopg2_pgevents import event from psycopg2_pgevents.sql import execute -from psycopg2_pgevents.event import Event, poll, register_event_channel, unregister_event_channel from psycopg2_pgevents.trigger import install_trigger, install_trigger_function @fixture def event_channel_registered(connection): - register_event_channel(connection) + event.register_event_channel(connection) @fixture def triggers_installed(connection): install_trigger_function(connection) - install_trigger(connection, 'settings') - install_trigger(connection, 'orders', schema='pointofsale') + install_trigger(connection, "settings") + install_trigger(connection, "orders", schema="pointofsale") class TestEvent: @@ -33,137 +32,131 @@ def test_event_fromjson(self): } """ - evt = Event.fromjson(json_string) - assert (evt.id == UUID('c2d29867-3d0b-d497-9191-18a9d8ee7830')) - assert (evt.type == 'insert') - assert (evt.schema_name == 'public') - assert (evt.table_name == 'widget') - assert (evt.row_id == '1') + evt = event.Event.fromjson(json_string) + assert evt.id == UUID("c2d29867-3d0b-d497-9191-18a9d8ee7830") + assert evt.type == "insert" + assert evt.schema_name == "public" + assert evt.table_name == "widget" + assert evt.row_id == "1" def test_event_tojson(self): - evt = Event( - 'c2d29867-3d0b-d497-9191-18a9d8ee7830', - 'insert', - 'public', - 'widget', - '1' - ) + evt = event.Event("c2d29867-3d0b-d497-9191-18a9d8ee7830", "insert", "public", "widget", "1") json_string = evt.tojson() json_dict = json.loads(json_string) - assert (json_dict['event_id'] == str(evt.id)) - assert (json_dict['event_type'] == evt.type) - assert (json_dict['schema_name'] == evt.schema_name) - assert (json_dict['table_name'] == evt.table_name) - assert (json_dict['row_id'] == evt.row_id) + assert json_dict["event_id"] == str(evt.id) + assert json_dict["event_type"] == evt.type + assert json_dict["schema_name"] == evt.schema_name + assert json_dict["table_name"] == evt.table_name + assert json_dict["row_id"] == evt.row_id def test_register_event_channel(self, connection): channel_registered = False - register_event_channel(connection) + event.register_event_channel(connection) - results = execute(connection, 'SELECT pg_listening_channels();') - if results and 'psycopg2_pgevents_channel' in results[0]: + results = execute(connection, "SELECT pg_listening_channels();") + if results and "psycopg2_pgevents_channel" in results[0]: channel_registered = True - assert (channel_registered == True) + assert channel_registered - @mark.usefixtures('event_channel_registered') + @mark.usefixtures("event_channel_registered") def test_unregister_event_channel(self, connection): channel_registered = True - unregister_event_channel(connection) + event.unregister_event_channel(connection) - results = execute(connection, 'SELECT pg_listening_channels();') + results = execute(connection, "SELECT pg_listening_channels();") if not results: channel_registered = False - assert (channel_registered == False) + assert not channel_registered - @mark.usefixtures('triggers_installed', 'event_channel_registered') + @mark.usefixtures("triggers_installed", "event_channel_registered") def test_poll_timeout(self, connection): - num_events = 0 + num_evts = 0 - for event in poll(connection): - num_events += 1 + for evt in event.poll(connection): + num_evts += 1 - assert (num_events == 0) + assert num_evts == 0 - @mark.usefixtures('triggers_installed', 'event_channel_registered') + @mark.usefixtures("triggers_installed", "event_channel_registered") def test_poll_public_schema_table_event(self, connection, client): execute(client, "INSERT INTO public.settings(key, value) VALUES('foo', 1);") - events = [event for event in poll(connection)] + evts = [evt for evt in event.poll(connection)] - assert (len(events) == 1) + assert len(evts) == 1 - event = events.pop(0) + evt = evts.pop(0) - assert (event.type == 'INSERT') - assert (event.schema_name == 'public') - assert (event.table_name == 'settings') + assert evt.type == "INSERT" + assert evt.schema_name == "public" + assert evt.table_name == "settings" - @mark.usefixtures('triggers_installed', 'event_channel_registered') + @mark.usefixtures("triggers_installed", "event_channel_registered") def test_poll_custom_schema_table_event(self, connection, client): execute(client, "INSERT INTO pointofsale.orders(description) VALUES('bar');") - events = [event for event in poll(connection)] + evts = [evt for evt in event.poll(connection)] - assert (len(events) == 1) + assert len(evts) == 1 - event = events.pop(0) + evt = evts.pop(0) - assert (event.type == 'INSERT') - assert (event.schema_name == 'pointofsale') - assert (event.table_name == 'orders') + assert evt.type == "INSERT" + assert evt.schema_name == "pointofsale" + assert evt.table_name == "orders" - @mark.usefixtures('triggers_installed', 'event_channel_registered') + @mark.usefixtures("triggers_installed", "event_channel_registered") def test_poll_event_event_types(self, connection, client): execute(client, "INSERT INTO public.settings(key, value) VALUES('foo', 1);") - events = [event for event in poll(connection)] + evts = [evt for evt in event.poll(connection)] - assert (len(events) == 1) + assert len(evts) == 1 - event = events.pop(0) + evt = evts.pop(0) - assert (event.type == 'INSERT') - assert (event.schema_name == 'public') - assert (event.table_name == 'settings') + assert evt.type == "INSERT" + assert evt.schema_name == "public" + assert evt.table_name == "settings" - execute(client, "UPDATE public.settings SET value = 2 WHERE id = {row_id};".format(row_id=event.row_id)) - execute(client, "DELETE FROM public.settings WHERE id = {row_id};".format(row_id=event.row_id)) + execute(client, "UPDATE public.settings SET value = 2 WHERE id = {row_id};".format(row_id=evt.row_id)) + execute(client, "DELETE FROM public.settings WHERE id = {row_id};".format(row_id=evt.row_id)) - events = [event for event in poll(connection)] + evts = [evt for evt in event.poll(connection)] - assert (len(events) == 2) + assert len(evts) == 2 - event = events.pop(0) + evt = evts.pop(0) - assert (event.type == 'UPDATE') - assert (event.schema_name == 'public') - assert (event.table_name == 'settings') + assert evt.type == "UPDATE" + assert evt.schema_name == "public" + assert evt.table_name == "settings" - event = events.pop(0) + evt = evts.pop(0) - assert (event.type == 'DELETE') - assert (event.schema_name == 'public') - assert (event.table_name == 'settings') + assert evt.type == "DELETE" + assert evt.schema_name == "public" + assert evt.table_name == "settings" - @mark.usefixtures('triggers_installed', 'event_channel_registered') - def test_poll_multiple_table_events(self, connection, client): + @mark.usefixtures("triggers_installed", "event_channel_registered") + def test_poll_multiple_table_evts(self, connection, client): execute(client, "INSERT INTO public.settings(key, value) VALUES('foo', 1);") execute(client, "INSERT INTO pointofsale.orders(description) VALUES('bar');") - events = [event for event in poll(connection)] + evts = [evt for evt in event.poll(connection)] - assert (len(events) == 2) + assert len(evts) == 2 - event = events.pop(0) - assert (event.type == 'INSERT') - assert (event.schema_name == 'public') - assert (event.table_name == 'settings') + evt = evts.pop(0) + assert evt.type == "INSERT" + assert evt.schema_name == "public" + assert evt.table_name == "settings" - event = events.pop(0) - assert (event.type == 'INSERT') - assert (event.schema_name == 'pointofsale') - assert (event.table_name == 'orders') + evt = evts.pop(0) + assert evt.type == "INSERT" + assert evt.schema_name == "pointofsale" + assert evt.table_name == "orders" diff --git a/tests/test_sql.py b/tests/test_sql.py index c6ad44a..e850d3d 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -5,21 +5,21 @@ class TestSql: def test_execute(self, connection): - results = execute(connection, 'SELECT * FROM pg_settings;') + results = execute(connection, "SELECT * FROM pg_settings;") - assert (results is not None) + assert results is not None def test_execute_programming_error(self, connection): programming_exception_raised = False try: # Raise exception by referencing a non-existent table - execute(connection, 'SELECT * FROM information_schema.triggerss;') + execute(connection, "SELECT * FROM information_schema.triggerss;") except ProgrammingError: programming_exception_raised = True - assert (programming_exception_raised == True) + assert programming_exception_raised def test_execute_no_response(self, connection): - results = execute(connection, 'SELECT * FROM information_schema.triggers;') + results = execute(connection, "SELECT * FROM information_schema.triggers;") - assert (results is None) + assert results is None diff --git a/tests/test_trigger.py b/tests/test_trigger.py index 9ad6dc6..21e2e2c 100644 --- a/tests/test_trigger.py +++ b/tests/test_trigger.py @@ -1,24 +1,23 @@ from psycopg2 import InternalError, ProgrammingError from pytest import fixture, mark +from psycopg2_pgevents import trigger from psycopg2_pgevents.sql import execute -from psycopg2_pgevents.trigger import install_trigger, install_trigger_function, trigger_function_installed, \ - trigger_installed, uninstall_trigger, uninstall_trigger_function @fixture def trigger_fn_installed(connection): - install_trigger_function(connection) + trigger.install_trigger_function(connection) @fixture def public_schema_trigger_installed(connection): - install_trigger(connection, 'settings') + trigger.install_trigger(connection, "settings") @fixture def custom_schema_trigger_installed(connection): - install_trigger(connection, 'orders', schema='pointofsale') + trigger.install_trigger(connection, "orders", schema="pointofsale") class TestTrigger: @@ -30,47 +29,47 @@ class TestTrigger: # - overwrite=True, prior_install=True def test_trigger_function_not_installed(self, connection): - installed = trigger_function_installed(connection) + installed = trigger.trigger_function_installed(connection) - assert (installed == False) + assert not installed - @mark.usefixtures('trigger_fn_installed') + @mark.usefixtures("trigger_fn_installed") def test_trigger_function_installed(self, connection): - installed = trigger_function_installed(connection) + installed = trigger.trigger_function_installed(connection) - assert (installed == True) + assert installed def test_add_trigger_function(self, connection): trigger_function_installed = False - install_trigger_function(connection) + trigger.install_trigger_function(connection) try: execute(connection, "SELECT pg_get_functiondef('public.psycopg2_pgevents_create_event'::regproc);") trigger_function_installed = True - except: + except: # noqa: E722 # Ignore error, its only use in this test is cause following # assertion to fail pass - assert (trigger_function_installed == True) + assert trigger_function_installed - @mark.usefixtures('trigger_fn_installed') + @mark.usefixtures("trigger_fn_installed") def test_remove_trigger_function(self, connection): trigger_function_installed = True - uninstall_trigger_function(connection) + trigger.uninstall_trigger_function(connection) try: execute(connection, "SELECT pg_get_functiondef('public.psycopg2_pgevents_create_event'::regproc);") except ProgrammingError: trigger_function_installed = False - assert (trigger_function_installed == False) + assert not trigger_function_installed - @mark.usefixtures('trigger_fn_installed') + @mark.usefixtures("trigger_fn_installed") def test_add_public_schema_trigger(self, connection): trigger_installed = False - install_trigger(connection, 'settings') + trigger.install_trigger(connection, "settings") try: statement = """ @@ -85,18 +84,18 @@ def test_add_public_schema_trigger(self, connection): result = execute(connection, statement) if result: trigger_installed = True - except: + except: # noqa: E722 # Ignore error, its only use in this test is cause following # assertion to fail pass - assert (trigger_installed == True) + assert trigger_installed - @mark.usefixtures('trigger_fn_installed') + @mark.usefixtures("trigger_fn_installed") def test_add_custom_schema_trigger(self, connection): trigger_installed = False - install_trigger(connection, 'orders', schema='pointofsale') + trigger.install_trigger(connection, "orders", schema="pointofsale") try: statement = """ @@ -111,51 +110,51 @@ def test_add_custom_schema_trigger(self, connection): result = execute(connection, statement) if result: trigger_installed = True - except: + except: # noqa: E722 # Ignore error, its only use in this test is cause following # assertion to fail pass - assert (trigger_installed == True) + assert trigger_installed def test_trigger_not_installed(self, connection): - installed = trigger_installed(connection, 'settings') + installed = trigger.trigger_installed(connection, "settings") - assert (installed == False) + assert not installed - @mark.usefixtures('trigger_fn_installed', 'public_schema_trigger_installed') + @mark.usefixtures("trigger_fn_installed", "public_schema_trigger_installed") def test_trigger_installed(self, connection): - installed = trigger_installed(connection, 'settings') + installed = trigger.trigger_installed(connection, "settings") - assert (installed == True) + assert installed - @mark.usefixtures('trigger_fn_installed', 'public_schema_trigger_installed') + @mark.usefixtures("trigger_fn_installed", "public_schema_trigger_installed") def test_remove_trigger_function_with_dependent_triggers(self, connection): trigger_function_removal_failed = False trigger_function_still_installed = False try: - uninstall_trigger_function(connection) + trigger.uninstall_trigger_function(connection) except InternalError: trigger_function_removal_failed = True try: execute(connection, "SELECT pg_get_functiondef('public.psycopg2_pgevents_create_event'::regproc);") trigger_function_still_installed = True - except: + except: # noqa: E722 # Ignore error, its only use in this test is cause following # assertion to fail pass - assert (trigger_function_removal_failed == True) - assert (trigger_function_still_installed == True) + assert trigger_function_removal_failed + assert trigger_function_still_installed - @mark.usefixtures('trigger_fn_installed', 'public_schema_trigger_installed') + @mark.usefixtures("trigger_fn_installed", "public_schema_trigger_installed") def test_force_remove_trigger_function_with_dependent_triggers(self, connection): trigger_function_still_installed = True trigger_installed = False - uninstall_trigger_function(connection, force=True) + trigger.uninstall_trigger_function(connection, force=True) try: execute(connection, "SELECT pg_get_functiondef('public.psycopg2_pgevents_create_event'::regproc);") @@ -175,14 +174,14 @@ def test_force_remove_trigger_function_with_dependent_triggers(self, connection) if not result: trigger_installed = False - assert (trigger_function_still_installed == False) - assert (trigger_installed == False) + assert not trigger_function_still_installed + assert not trigger_installed - @mark.usefixtures('trigger_fn_installed', 'public_schema_trigger_installed') + @mark.usefixtures("trigger_fn_installed", "public_schema_trigger_installed") def test_remove_public_schema_trigger(self, connection): trigger_installed = True - uninstall_trigger(connection, 'settings') + trigger.uninstall_trigger(connection, "settings") statement = """ SELECT * @@ -196,13 +195,13 @@ def test_remove_public_schema_trigger(self, connection): if not result: trigger_installed = False - assert (trigger_installed == False) + assert not trigger_installed - @mark.usefixtures('trigger_fn_installed', 'custom_schema_trigger_installed') + @mark.usefixtures("trigger_fn_installed", "custom_schema_trigger_installed") def test_remove_custom_schema_trigger(self, connection): trigger_installed = True - uninstall_trigger(connection, 'orders', schema='pointofsale') + trigger.uninstall_trigger(connection, "orders", schema="pointofsale") statement = """ SELECT * @@ -216,4 +215,4 @@ def test_remove_custom_schema_trigger(self, connection): if not result: trigger_installed = False - assert (trigger_installed == False) + assert not trigger_installed