Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre-commit Linting and Type checking #2

Merged
merged 2 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: pre-commit

on:
pull_request:
push:
branches: [main]

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.0
55 changes: 55 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
exclude: "helm"
default_stages: [commit]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: check-builtin-literals
- id: check-case-conflict
- id: detect-private-key

- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.0.3"
hooks:
- id: prettier
args: ["--tab-width", "2"]

- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
hooks:
- id: pyupgrade
args: [--py311-plus]
exclude: hooks/

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
hooks:
- id: mypy
additional_dependencies:
- pydantic==1.10.12
- requests==2.31
- fastapi==0.99
- dependency_injector==4.41.0
- langchain==0.0.264
args: [--install-types, --non-interactive]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.287
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]

- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black

ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
34 changes: 21 additions & 13 deletions ddd_framework/domain/model.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

from abc import ABC
from datetime import datetime
from typing import Any, Optional, Protocol, Type, TypeVar, Union
from typing import Any, Protocol, TypeVar

from attr import define, field, make_class
import cattrs
from attr import define, field, make_class

from ddd_framework.utils.types import get_generic_type

Expand All @@ -16,6 +18,9 @@ class Event:
time: datetime = field(factory=datetime.now)


# endregion


# region "Value Object"
@define(kw_only=True)
class ValueObject:
Expand All @@ -24,28 +29,30 @@ class ValueObject:

# endregion


# region "Identity"
@define(kw_only=False, frozen=True)
class Id(ValueObject):
"""An identifier"""

id: Union[str, int]
id: str | int

@classmethod
def from_raw_id(cls, raw_id: Union[str, int]) -> 'Id':
def from_raw_id(cls, raw_id: str | int) -> Id:
return cls(id=raw_id)


def NewId(name: str, base: Id = Id):
def NewId(name: str, base: type[Id] = Id) -> type[Id]:
"""Create a new identifier's type."""
return make_class(name, attrs={}, bases=(base,))


def structure_id(value: Any, _klass: Type) -> Id:
def structure_id(value: Any, _klass: type[Id]) -> Id:
if isinstance(value, dict):
return _klass(**value)
return _klass(id=value)


cattrs.register_structure_hook(Id, structure_id)


Expand All @@ -57,10 +64,11 @@ def structure_id(value: Any, _klass: Type) -> Id:
class Entity:
"""Represent an entity."""

id: Optional[Id] = field(default=None)
id: Id | None = field(default=None)

def __hash__(self):
return hash(self.id.id)
def __hash__(self) -> int:
# TODO: Fix "Item "None" of "Id | None" has no attribute "id""
return hash(self.id.id) # type: ignore


# endregion
Expand All @@ -76,15 +84,15 @@ class Aggregate(ABC, Entity):


# region "Repository"
AggregateType = TypeVar('AggregateType', bound=Aggregate, covariant=True)
AggregateType_co = TypeVar('AggregateType_co', bound=Aggregate, covariant=True)


class IRepository(Protocol[AggregateType]):
class IRepository(Protocol[AggregateType_co]):
"""A domain interface of an aggregate's repository."""

@property
def aggregate_cls(self) -> AggregateType:
return get_generic_type(self.__class__)
def aggregate_cls(self) -> AggregateType_co:
return get_generic_type(self.__class__) # type: ignore


# endregion
22 changes: 14 additions & 8 deletions ddd_framework/infrastructure/persistance/mongo.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from functools import cached_property
from typing import Any, Mapping, Optional, Sequence, Union
from typing import TYPE_CHECKING, Any, ClassVar

import attrs
import pymongo.database
from pymongo import IndexModel
from pymongo.collection import Collection

if TYPE_CHECKING:
from collections.abc import Mapping, Sequence

from pymongo.collection import Collection


@attrs.define
class Index:
"""Represent a Mongo index

:cvar keys: Document fields to add to an index. Mirrors `keys` from :class:`pymongo.collection.IndexModel`
:cvar unique: Unique constraint, boolean
:var keys: Document fields to add to an index. Mirrors `keys` from :class:`pymongo.collection.IndexModel`
:var unique: Unique constraint, boolean
"""

keys: Union[str, Sequence[tuple[str, Union[int, str, Mapping[str, Any]]]]]
keys: str | Sequence[tuple[str, int | str | Mapping[str, Any]]]
unique: bool = False

def to_dict(self) -> dict:
def to_dict(self) -> dict[str, Any]:
return attrs.asdict(self)


Expand All @@ -34,8 +40,8 @@ class MongoRepositoryMixin(ABC):
meaning that all the old indexes will be deleted and new added.
"""

collection_name: str = None
indexes: Optional[list[Index]] = None
collection_name: ClassVar[str]
indexes: ClassVar[list[Index] | None] = None

@abstractmethod
def get_database(self) -> pymongo.database.Database:
Expand Down
6 changes: 4 additions & 2 deletions ddd_framework/utils/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import get_args
from __future__ import annotations

from typing import Any, get_args

def get_generic_type(cls: type) -> type:

def get_generic_type(cls: type[Any]) -> Any:
return get_args(cls.__orig_bases__[0])[0]
115 changes: 115 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
[project]
name = "docsie-ddd-framework"
version = "0.0.1"
description = "The package includes helper methods and types for working in DDD"
readme = "README.md"
requires-python = ">=3.8"
license = { file = "LICENSE" }
authors = [
{ name = "Philippe Trounev", email = "philippe.trounev@likalo.com" },
{ name = "Nikita Belyaev", email = "nick@docsie.io" },
]
maintainers = [
{ name = "Philippe Trounev", email = "philippe.trounev@likalo.com" },
{ name = "Nikita Belyaev", email = "nick@docsie.io" },
]
classifiers = ["Programming Language :: Python"]
dependencies = ["cattrs~=23.1.0", "attrs~=23.1.0", "pymongo~=4.3.3"]

[project.optional-dependencies]
dev = ["pre-commit", "mypy", "ruff"]

[project.urls]
Repository = "https://github.com/LikaloLLC/ddd-framework.git"

[tool.setuptools.packages]
find = {}

[tool.mypy]
strict = true
explicit_package_bases = true
packages = ["ddd_framework"]
ignore_missing_imports = true
exclude = ['ddd_framework/utils/types.py']

[tool.ruff]
src = ["ddd_frameowrk"]

select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"C", # flake8-comprehensions
"B", # flake8-bugbear
"ANN", # flake8-annotations
"FA", # flake8-future-annotations
"T20", # flake8-print
"Q", # flake8-quotes
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
"PL", # Pylint
]
ignore = [
"E501", # line too long, handled by black
"B008", # do not perform function calls in argument defaults
"C901", # too complex
"ANN101", # Missing type annotation for `self` in method
"ANN002", # Missing type annotation for `*args`
"ANN003", # Missing type annotation for `**kwargs`
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in ... TODO: It's better to bring it back
"ANN102", # Missing type annotation for `cls` in classmethod
"PLR0913", # Too many arguments to function call (7 > 5)
"PLW1508", #Invalid type for environment variable default; expected `str` or `None`
]

# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]

# Same as Black.
line-length = 128

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

target-version = "py38"

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]

[tool.ruff.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "single"
multiline-quotes = "single"


[tool.black]
line-length = 128
skip-string-normalization = true
target-version = ['py38']
18 changes: 0 additions & 18 deletions setup.py

This file was deleted.