Skip to content

Commit

Permalink
add scripts, CONTRIBUTING, increase tests cov
Browse files Browse the repository at this point in the history
  • Loading branch information
Lancetnik committed Apr 5, 2023
1 parent 047d196 commit 2f18098
Show file tree
Hide file tree
Showing 14 changed files with 157 additions and 46 deletions.
77 changes: 77 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Developing

If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.

### Virtual environment with `venv`

You can create a virtual environment in a directory using Python's `venv` module:

```bash
$ python -m venv venv
```

That will create a directory `./venv/` with the Python binaries and then you will be able to install packages for that isolated environment.

### Activate the environment

Activate the new environment with:

```bash
$ source ./venv/bin/activate
```

Make sure you have the latest pip version on your virtual environment to
```bash
$ python -m pip install --upgrade pip
```

### pip

After activating the environment as described above:

```bash
$ pip install -e ."[dev]"
```

It will install all the dependencies and your local Propan in your local environment.

#### Using your local Propan

If you create a Python file that imports and uses Propan, and run it with the Python from your local environment, it will use your local Propan source code.

And if you update that local Propan source code, as it is installed with `-e`, when you run that Python file again, it will use the fresh version of Propan you just edited.

That way, you don't have to "install" your local version to be able to test every change.

To use your local Propan cli type:
```bash
$ python -m propan ...
```

### Tests

To run tests with your current Propan application and Python environment use:
```bash
$ bash ./scripts/test-cov.sh
```

To run all tests based on RabbitMQ, NATS or another dependencies you should run first following *docker-compose.yml*

```yaml
version: "3"

services:
rabbit:
image: rabbitmq
ports:
- 5672:5672

nats:
image: nats
ports:
- 4222:4222
```
```bash
$ docker compose up -d
```
2 changes: 1 addition & 1 deletion propan/__about__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Simple and fast framework to create message brokers based microservices"""

__version__ = "0.0.8.3"
__version__ = "0.0.8.4"
36 changes: 15 additions & 21 deletions propan/brokers/model/broker_usecase.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from abc import ABC
from abc import ABC, abstractmethod
import logging
from time import perf_counter
from functools import wraps
Expand All @@ -20,14 +20,10 @@ def __init__(self,
apply_types: bool = True,
logger: Optional[logging.Logger] = access_logger,
log_level: int = logging.INFO,
log_fmt: Optional[str] = None,
**kwargs):
self.logger = logger
self.log_level = log_level

if log_fmt:
self._fmt = log_fmt

self._is_apply_types = apply_types
self._connection_args = args
self._connection_kwargs = kwargs
Expand All @@ -39,35 +35,33 @@ async def connect(self, *args, **kwargs):
if self._connection is None:
_args = args or self._connection_args
_kwargs = kwargs or self._connection_kwargs

try:
self._connection = await self._connect(*_args, **_kwargs)
except Exception as e:
if self.logger:
self.logger.error(e)
exit()

self._connection = await self._connect(*_args, **_kwargs)
return self._connection

@abstractmethod
async def _connect(self, *args, **kwargs) -> Any:
raise NotImplementedError()

async def start(self) -> None:
self._init_logger()
await self.connect()

@abstractmethod
def publish_message(self, queue_name: str, message: str) -> None:
raise NotImplementedError()

@abstractmethod
def close(self) -> None:
raise NotImplementedError()

@abstractmethod
def _decode_message(self) -> Union[str, dict]:
raise NotImplementedError()

@abstractmethod
def _process_message(self, func: Callable, watcher: Optional[BaseWatcher]) -> Callable:
raise NotImplementedError()

async def start(self) -> None:
self._init_logger()
await self.connect()

def _get_log_context(self, *kwargs) -> dict[str, Any]:
return {}

Expand All @@ -82,12 +76,12 @@ def _init_logger(self):
for handler in self.logger.handlers:
handler.setFormatter(type(handler.formatter)(self.fmt))

def __enter__(self) -> 'BrokerUsecase':
self.connect()
async def __aenter__(self) -> 'BrokerUsecase':
await self.connect()
return self

def __exit__(self, *args, **kwargs):
self.close()
async def __aexit__(self, *args, **kwargs):
await self.close()

def _wrap_handler(self,
func: Callable,
Expand Down
3 changes: 0 additions & 3 deletions propan/brokers/model/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ class NameRequired(BaseModel):
def __init__(self, name: str, **kwargs):
super().__init__(name=name, **kwargs)

def __hash__(self):
return hash(self.json())

def __eq__(self, other: 'NameRequired') -> bool:
return self.name == other.name

Expand Down
7 changes: 0 additions & 7 deletions propan/brokers/nats/nats_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ def __init__(
super().__init__(*args, **kwargs)
self._fmt = log_fmt

async def __aenter__(self) -> 'NatsBroker':
await self.connect()
return self

async def __aexit__(self, *args, **kwargs):
await self.close()

async def _connect(self, *args, **kwargs) -> Client:
return await nats.connect(*args, **kwargs)

Expand Down
5 changes: 4 additions & 1 deletion propan/brokers/push_back_watcher.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from abc import ABC
from abc import ABC, abstractmethod
from logging import Logger
from collections import Counter
from typing import Optional, Type, Callable
Expand All @@ -12,12 +12,15 @@ def __init__(self, max_tries: int = None, logger: Optional[Logger] = None):
self.logger = logger
self.max_tries = max_tries

@abstractmethod
def add(self, message_id: str) -> None:
raise NotImplementedError()

@abstractmethod
def is_max(self, message_id: str) -> bool:
raise NotImplementedError()

@abstractmethod
def remove(self, message_id: str):
raise NotImplementedError()

Expand Down
3 changes: 0 additions & 3 deletions propan/brokers/rabbit/rabbit_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ async def __aenter__(self) -> 'RabbitBroker':
await self.connect()
await self.init_channel()
return self

async def __aexit__(self, *args, **kwargs):
await self.close()

async def _connect(self, *args, **kwargs) -> aio_pika.Connection:
return await aio_pika.connect_robust(
Expand Down
34 changes: 28 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ propan = "propan:cli"

[project.optional-dependencies]
async_rabbit = [
"pika-stubs",
"aio-pika>=9",
]

Expand All @@ -75,18 +76,15 @@ test = [
"propan[async_rabbit]",
"propan[async_nats]",

"distutils",
"ruff",

"coverage[toml]",
"pytest",
"pytest-asyncio",
"pytest-cov",
]

dev = [
"propan[test]",

"pika-stubs",

"loguru",
]

[tool.hatch.metadata]
Expand Down Expand Up @@ -117,3 +115,27 @@ features = [

[[tool.hatch.envs.test.matrix]]
python = ["37", "38", "39", "310", "311"]

[tool.coverage.run]
parallel = true
source = [
"propan",
"tests"
]
context = '${CONTEXT}'

[tool.coverage.report]
show_missing = true
skip_empty = true
exclude_lines = [
"if __name__ == .__main__.:",
"from .*",
"import .*",
'@(abc\.)?abstractmethod',
"raise NotImplementedError",
'raise AssertionError',
'if self.logger is not None:',
'logger\..*',
"pass",
'\.\.\.',
]
3 changes: 3 additions & 0 deletions scripts/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
hatch clean
hatch build
hatch publish
4 changes: 4 additions & 0 deletions scripts/test-cov.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bash scripts/test.sh ${@}
coverage combine
coverage report --show-missing
coverage html
1 change: 1 addition & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage run -m pytest tests ${@}
19 changes: 18 additions & 1 deletion tests/brokers/base/test_pushback.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


@pytest.mark.asyncio
async def test_push_not_push(async_mock: AsyncMock):
async def test_push_back_correct(async_mock: AsyncMock):
message_id = 1

watcher = PushBackWatcher(3)
Expand All @@ -25,6 +25,23 @@ async def test_push_not_push(async_mock: AsyncMock):
assert not watcher.memory.get(message_id)


@pytest.mark.asyncio
async def test_push_back_endless_correct(async_mock: AsyncMock):
message_id = 1

watcher = FakePushBackWatcher()

context = WatcherContext(watcher, message_id,
on_success=async_mock.on_success,
on_error=async_mock.on_error,
on_max=async_mock.on_max)

async with context:
await async_mock()

async_mock.on_success.assert_awaited_once()


@pytest.mark.asyncio
async def test_push_back_watcher(async_mock: AsyncMock):
watcher = PushBackWatcher(3)
Expand Down
7 changes: 5 additions & 2 deletions tests/utils/context/test_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ async def test_nested_context_alias(context: Context):
context.set_context("model", model)

@use_context
async def func(*args, m = Alias("model.field.field"), **kwargs):
return m is model.field.field
async def func(*args,
m = Alias("model.field.field"),
m2 = Alias("model.not_existed"),
**kwargs):
return m is model.field.field and m2 is None

assert await func(model=model)

Expand Down
2 changes: 1 addition & 1 deletion tests/utils/context/test_depens.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def test_sync_with_async_depends():

@use_context
def func(*args, k = Depends(async_dep), **kwargs):
return k is key
pass

with pytest.raises(ValueError):
func(key=key)
Expand Down

0 comments on commit 2f18098

Please sign in to comment.