Skip to content

Commit

Permalink
add testing workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
pgorecki authored Mar 4, 2024
2 parents 0e301a9 + 87cd5e3 commit ccf630c
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 63 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Release

on:
push:
tags:
- '*.*.*'

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y
- name: Update PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Build project for distribution
run: poetry build

- name: Check Version
id: check-version
run: |
[[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || echo prerelease=true >> $GITHUB_OUTPUT
- name: Create Release
uses: ncipollo/release-action@v1
with:
artifacts: "dist/*"
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
prerelease: steps.check-version.outputs.prerelease == 'true'

- name: Publish to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
run: poetry publish
75 changes: 75 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Tests

on:
pull_request: {}
push:
branches: [main,workflows]

env:
PYTHONWARNDEFAULTENCODING: 'true'

jobs:
tests:
name: ${{ matrix.os }} / ${{ matrix.python-version }}
runs-on: "${{ matrix.os }}-latest"
strategy:
matrix:
os: [Ubuntu, MacOS, Windows]
python-version: ["3.9", "3.10", "3.11", "3.12"]
include:
- os: Ubuntu
python-version: pypy-3.8
fail-fast: false
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Get full Python version
id: full-python-version
run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT

- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y
- name: Update PATH
if: ${{ matrix.os != 'Windows' }}
run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Update Path for Windows
if: ${{ matrix.os == 'Windows' }}
run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH

- name: Set up cache
uses: actions/cache@v4
id: cache
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}

- name: Ensure cache is healthy
if: steps.cache.outputs.cache-hit == 'true'
run: |
# `timeout` is not available on macOS, so we define a custom function.
[ "$(command -v timeout)" ] || function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }
# Using `timeout` is a safeguard against the Poetry command hanging for some reason.
timeout 10s poetry run pip --version || rm -rf .venv
- name: Check lock file
run: poetry lock --check

- name: Install dependencies
run: poetry install --without examples

- name: Run tests
run: poetry run python -m pytest -p no:sugar -q tests/

- name: Run mypy
run: poetry run mypy lato
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Change Log


## [0.9.0] - 2024-02-28

### Changed

- Rename `Task` to `Command`
- Rename `Application.emit()` to `Application.publish()`
- Rename `TransactionContext.emit()` to `TransactionContext.publish()`
- Remove `Application.on()` decorator in favor of `Application.handler()`

## [0.8.0] - 2024-01-08

No history.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[![](https://github.com/pgorecki/lato/workflows/Tests/badge.svg)](https://github.com/pgorecki/lato/actions?query=workflow%3ATests)
[![Documentation Status](https://readthedocs.org/projects/lato/badge/?version=latest)](https://lato.readthedocs.io/en/latest/?badge=latest)

[![PyPI version](https://img.shields.io/pypi/v/lato)](https://pypi.org/project/lato/)
[![Python Versions](https://img.shields.io/pypi/pyversions/lato)](https://pypi.org/project/lato/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://static.pepy.tech/badge/lato/month)](https://pepy.tech/project/lato)

# Lato

Expand Down
2 changes: 1 addition & 1 deletion lato/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

def add_stderr_logger(
level: int = logging.DEBUG,
) -> logging.StreamHandler[typing.TextIO]:
) -> logging.StreamHandler:
"""
Helper for quickly adding a StreamHandler to the logger. Useful for
debugging.
Expand Down
16 changes: 8 additions & 8 deletions lato/application.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from collections.abc import Callable
from typing import Any

from typing import Any, Optional, Union, List
from lato.types import DependencyIdentifier
from lato.application_module import ApplicationModule
from lato.dependency_provider import BasicDependencyProvider, DependencyProvider
from lato.message import Command, Event, Message
Expand All @@ -24,7 +24,7 @@ class Application(ApplicationModule):
def __init__(
self,
name=__name__,
dependency_provider: DependencyProvider | None = None,
dependency_provider: Optional[DependencyProvider] = None,
**kwargs,
):
"""Initialize the application instance.
Expand All @@ -42,10 +42,10 @@ def __init__(
self._transaction_context_factory = None
self._on_enter_transaction_context = lambda ctx: None
self._on_exit_transaction_context = lambda ctx, exception=None: None
self._transaction_middlewares = []
self._composers: dict[str | Command, Callable] = {}
self._transaction_middlewares: List[Callable] = []
self._composers: dict[Union[Message, str], Callable] = {}

def get_dependency(self, identifier: str | type) -> Any:
def get_dependency(self, identifier: DependencyIdentifier) -> Any:
"""Gets a dependency from the dependency provider. Dependencies can be resolved either by name or by type.
:param identifier: A string or a type representing the dependency.
Expand All @@ -54,10 +54,10 @@ def get_dependency(self, identifier: str | type) -> Any:
"""
return self.dependency_provider.get_dependency(identifier)

def __getitem__(self, identifier: str | type) -> Any:
def __getitem__(self, identifier: DependencyIdentifier) -> Any:
return self.get_dependency(identifier)

def call(self, func: Callable | str, *args, **kwargs):
def call(self, func: Union[Callable, str], *args, **kwargs):
"""Invokes a function with `args` and `kwargs` within the :class:`TransactionContext`.
If `func` is a string, then it is an alias, and the corresponding handler for the alias is retrieved.
Any missing arguments are provided by the dependency provider of a transaction context,
Expand Down
14 changes: 7 additions & 7 deletions lato/application_module.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging
from collections import defaultdict
from collections.abc import Callable

from lato.message import Message
from lato.types import HandlerAlias
from lato.utils import OrderedSet

log = logging.getLogger(__name__)
Expand All @@ -28,7 +28,7 @@ def include_submodule(self, a_module: "ApplicationModule"):
), f"Can only include {ApplicationModule} instances, got {a_module}"
self._submodules.add(a_module)

def handler(self, alias: type[Message] | str):
def handler(self, alias: HandlerAlias):
"""
Decorator for registering a handler. Handler can be aliased by a name or by a message type.
Expand Down Expand Up @@ -65,12 +65,12 @@ def handler(self, alias: type[Message] | str):
command handler called
"""
try:
is_message = issubclass(alias, Message)
except TypeError:
is_message = False
if isinstance(alias, type):
is_message_type = issubclass(alias, Message)
else:
is_message_type = False

if callable(alias) and not is_message:
if callable(alias) and not is_message_type:
# decorator was called without any argument
func = alias
alias = func.__name__
Expand Down
2 changes: 1 addition & 1 deletion lato/compositon.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from operator import add, or_
from typing import Any, Optional

from mergedeep import Strategy, merge
from mergedeep import Strategy, merge # type: ignore

additive_merge = partial(merge, strategy=Strategy.TYPESAFE_ADDITIVE)

Expand Down
13 changes: 7 additions & 6 deletions lato/dependency_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Callable
from typing import Any

from lato.types import DependencyIdentifier
from lato.utils import OrderedDict


Expand Down Expand Up @@ -47,7 +48,7 @@ class DependencyProvider(ABC):
allow_types = True

@abstractmethod
def has_dependency(self, identifier: str | type) -> bool:
def has_dependency(self, identifier: DependencyIdentifier) -> bool:
"""
Check if a dependency with the given identifier exists.
Expand All @@ -57,7 +58,7 @@ def has_dependency(self, identifier: str | type) -> bool:
raise NotImplementedError()

@abstractmethod
def register_dependency(self, identifier: str | type, dependency: Any):
def register_dependency(self, identifier: DependencyIdentifier, dependency: Any):
"""
Register a dependency with a given identifier (name or type).
Expand All @@ -66,7 +67,7 @@ def register_dependency(self, identifier: str | type, dependency: Any):
"""
raise NotImplementedError()

def get_dependency(self, identifier: str | type) -> Any:
def get_dependency(self, identifier: DependencyIdentifier) -> Any:
"""
Retrieve a dependency using its identifier (name or type).
Expand Down Expand Up @@ -205,7 +206,7 @@ def __init__(self, *args, **kwargs):
self._dependencies = {}
self.update(*args, **kwargs)

def register_dependency(self, identifier: str | type, dependency: Any):
def register_dependency(self, identifier: DependencyIdentifier, dependency: Any):
"""
Register a dependency with a given identifier (name or type).
Expand All @@ -217,7 +218,7 @@ def register_dependency(self, identifier: str | type, dependency: Any):

self._dependencies[identifier] = dependency

def has_dependency(self, identifier: str | type) -> bool:
def has_dependency(self, identifier: DependencyIdentifier) -> bool:
"""
Check if a dependency with the given identifier exists.
Expand All @@ -226,7 +227,7 @@ def has_dependency(self, identifier: str | type) -> bool:
"""
return identifier in self._dependencies

def get_dependency(self, identifier: str | type) -> Any:
def get_dependency(self, identifier: DependencyIdentifier) -> Any:
"""
Retrieve a dependency using its identifier (name or type).
Expand Down
15 changes: 8 additions & 7 deletions lato/testing.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
import contextlib
from typing import Iterator

from lato import Application


@contextlib.contextmanager
def override_app(application: Application, *args, **kwargs) -> Application:
def override_app(application: Application, *args, **kwargs) -> Iterator[Application]:
original_dependency_provider = application.dependency_provider
overriden_dependency_provider = original_dependency_provider.copy(*args, **kwargs)
overridden_dependency_provider = original_dependency_provider.copy(*args, **kwargs)

application.dependency_provider = overriden_dependency_provider
application.dependency_provider = overridden_dependency_provider
yield application

application.dependency_provider = original_dependency_provider


@contextlib.contextmanager
def override_ctx(application: Application, *args, **kwargs) -> Application:
def override_ctx(application: Application, *args, **kwargs) -> Iterator[Application]:
original_transaction_context = application.transaction_context

def overriden_transaction_context(**dependencies):
ctx = original_transaction_context(**dependencies)
ctx.dependency_provider = ctx.dependency_provider.copy(*args, **kwargs)
return ctx

application.transaction_context = overriden_transaction_context
application.transaction_context = overriden_transaction_context # type: ignore
yield application

application.transaction_context = original_transaction_context
application.transaction_context = original_transaction_context # type: ignore


@contextlib.contextmanager
def override(application: Application, *args, **kwargs) -> Application:
def override(application: Application, *args, **kwargs) -> Iterator[Application]:
with override_app(application, **kwargs) as overridden1:
with override_ctx(overridden1, **kwargs) as overridden2:
yield overridden2
Loading

0 comments on commit ccf630c

Please sign in to comment.