Skip to content

Commit

Permalink
Add linting to project
Browse files Browse the repository at this point in the history
- Refactor Dockerfile
- Refactor makefile
- Update pipfile
- Update pipfile.lock
- create .flake8 file
- create pyproject.toml
- update test.yml from github actions
- remove python 3.7 support
- add python 3.9 support
  • Loading branch information
Guille-Barrena committed May 24, 2023
1 parent b964290 commit 7b71bb9
Show file tree
Hide file tree
Showing 25 changed files with 487 additions and 167 deletions.
3 changes: 3 additions & 0 deletions .flake8
@@ -0,0 +1,3 @@
[flake8]
ignore = E203, W503, E231, E501

4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.7', '3.8' ]
python-version: [ '3.8', '3.9' ]
name: Pypendency tests on ${{ matrix.python-version }}
steps:
- uses: actions/checkout@master
Expand All @@ -20,6 +20,8 @@ jobs:
python -m pip install --upgrade pip
pip install pipenv
pipenv install --dev
- name: Check code quality
run: make lint docker=0
- name: Unittest
env:
PYTHONPATH: ${PYTHONPATH}:${PWD}:src/
Expand Down
21 changes: 14 additions & 7 deletions Dockerfile
@@ -1,12 +1,19 @@
FROM python:3.7
ARG PYTHON_VERSION=3.8.16
FROM python:$PYTHON_VERSION

ENV PYTHONPATH /usr/src/app/src
ENV PYTHONPATH /app/src:/app/tests
ENV PATH /root/.local/bin/:$PYENV_ROOT/bin/:$PATH

WORKDIR /usr/src/app

COPY Pipfile /usr/src/app/Pipfile
COPY Pipfile.lock /usr/src/app/Pipfile.lock
WORKDIR /app/
COPY . /app/

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --upgrade pip \
&& adduser -u 1000 --gecos "" --disabled-password fever \
&& chown -R fever:fever /app
RUN pip install pipenv
RUN pipenv install --dev --system --verbose

RUN pipenv install --dev
CMD ["python"]
23 changes: 22 additions & 1 deletion Makefile
@@ -1,2 +1,23 @@
ifneq ($(docker),0)
DOCKER_CMD := docker build -t pypendency-image . && docker run --rm -v $(PWD):/app pypendency-image
endif

run-tests:
python -m unittest
$(DOCKER_CMD) python -m unittest

pipenv-lock:
$(DOCKER_CMD) python -m pipenv lock

format:
$(DOCKER_CMD) python -m black --config=pyproject.toml src/ tests/

flake8:
$(DOCKER_CMD) python -m flake8 --config=.flake8 src/ tests/

mypy:
$(DOCKER_CMD) python -m mypy --config-file=pyproject.toml src/ tests/

black:
$(DOCKER_CMD) python -m black --config=pyproject.toml --check src/ tests/

lint: flake8 black mypy
6 changes: 6 additions & 0 deletions Pipfile
Expand Up @@ -4,6 +4,12 @@ url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
black = "==23.1.0"
flake8 = "==6.0.0"
mypy = "==1.0.0"

[packages]
pyyaml = "*"
black = "==23.1.0"
flake8 = "==6.0.0"
mypy = "==1.0.0"
307 changes: 305 additions & 2 deletions Pipfile.lock

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions pyproject.toml
@@ -0,0 +1,18 @@
[tool.black]
line-length = 120
target_version = ['py38', 'py39']
include = '\.pyi?$'
exclude = '''
/(
| \.git
| \.mypy_cache
| \.tox
| \.venv
)/
'''

[tool.mypy]
ignore_missing_imports = true
exclude = [
'yaml'
]
4 changes: 1 addition & 3 deletions src/pypendency/builder.py
Expand Up @@ -26,9 +26,7 @@ def set_definition(self, definition: Definition) -> None:
if self.has(definition.identifier):
raise exceptions.ServiceAlreadyDefined(definition.identifier)

self._service_mapping.update({
definition.identifier: definition
})
self._service_mapping.update({definition.identifier: definition})


container_builder = ContainerBuilder.get_container_instance()
16 changes: 9 additions & 7 deletions src/pypendency/container.py
Expand Up @@ -11,22 +11,24 @@

class AbstractContainer(ABC):
@abstractmethod
def set(self, identifier: str, service: object) -> None: pass
def set(self, identifier: str, service: object) -> None:
pass

@abstractmethod
def get(self, identifier: str) -> Optional[object]: pass
def get(self, identifier: str) -> Optional[object]:
pass

@abstractmethod
def has(self, identifier: str) -> bool: pass
def has(self, identifier: str) -> bool:
pass


class Container(AbstractContainer):
def __init__(self, definitions: List[Definition]):
self._resolved = False
self._on_resolved_callbacks: Set[OnContainerResolvedCallable] = set()
self._service_mapping: Dict[str, Union[None, object, Definition]] = {
definition.identifier: definition
for definition in definitions
definition.identifier: definition for definition in definitions
}
self._tags_mapping: Dict[Tag, List[str]] = {}

Expand All @@ -49,7 +51,7 @@ def __populate_tags_map(self) -> None:
self.__add_service_to_tag_group(tag, service.identifier)

def __add_service_to_tag_group(self, tag: Tag, service_identifier: str) -> None:
self._tags_mapping.setdefault(tag, set()).add(service_identifier)
self._tags_mapping.setdefault(tag, list()).append(service_identifier)

def __perform_on_resolved_callbacks(self) -> None:
for on_resolved_callback in self._on_resolved_callbacks:
Expand Down Expand Up @@ -145,7 +147,7 @@ def __instance_from_fqn(self, fully_qualified_name: str, args: list, kwargs: dic
raise exceptions.ServiceNotFoundFromFullyQualifiedName(fully_qualified_name)

try:
return klass(*args, **kwargs)
return klass(*args, **kwargs) # type: ignore
except TypeError as e:
raise exceptions.ServiceInstantiationFailed(fully_qualified_name) from e

Expand Down
2 changes: 1 addition & 1 deletion src/pypendency/definition.py
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import List, Dict, Set
from typing import List, Set

from pypendency.argument import Argument
from pypendency.tag import Tag
Expand Down
19 changes: 6 additions & 13 deletions src/pypendency/exceptions.py
@@ -1,6 +1,3 @@
from pypendency.tag import Tag


class ContainerAlreadyResolved(Exception):
def __init__(self):
super().__init__("Container already resolved can't be resolved again")
Expand All @@ -14,25 +11,20 @@ def __init__(self):
class ServiceAlreadyDefined(Exception):
def __init__(self, identifier: str):
self.identifier = identifier
super().__init__(
f"The service identified by {identifier} has already been defined in container"
)
super().__init__(f"The service identified by {identifier} has already been defined in container")


class ServiceNotFoundInContainer(Exception):
def __init__(self, identifier: str):
self.identifier = identifier
super().__init__(
f"The service identified by {identifier} has not been defined in container"
)
super().__init__(f"The service identified by {identifier} has not been defined in container")


class ServiceNotFoundFromFullyQualifiedName(Exception):
def __init__(self, fully_qualified_name: str):
self.fully_qualified_name = fully_qualified_name
super().__init__(
f"Container can't locate any class in {fully_qualified_name}"
)
super().__init__(f"Container can't locate any class in {fully_qualified_name}")


class ServiceInstantiationFailed(Exception):
def __init__(self, service_fqn: str) -> None:
Expand All @@ -45,6 +37,7 @@ def __init__(self, tag_identifier: str) -> None:
self.tag_identifier = tag_identifier
super().__init__(f"The tag '{tag_identifier}' does not exist in the container")


class PypendencyCallbackException(Exception):
def __init__(self) -> None:
super().__init__(f"Exception on_resolved_callback")
super().__init__("Exception on_resolved_callback")
19 changes: 6 additions & 13 deletions src/pypendency/loaders/exceptions.py
@@ -1,33 +1,26 @@
class ContainerLoaderError(Exception): pass
class ContainerLoaderError(Exception):
pass


class ResourceNotFound(ContainerLoaderError):
def __init__(self, resource: str):
self.resource = resource
super().__init__(
f"Resource {str(resource)} has not been found"
)
super().__init__(f"Resource {str(resource)} has not been found")


class ParsingErrorOnResource(ContainerLoaderError):
def __init__(self, resource: str):
self.resource = resource
super().__init__(
f"Resource {str(resource)} can't be parsed"
)
super().__init__(f"Resource {str(resource)} can't be parsed")


class MissingLoaderMethod(ContainerLoaderError):
def __init__(self, resource: str):
self.resource = resource
super().__init__(
f"Resource {str(resource)} requires loader method"
)
super().__init__(f"Resource {str(resource)} requires loader method")


class PathNotAbsolute(Exception):
def __init__(self, path: str):
self.path = path
super().__init__(
f"Path is not absolute: {path}"
)
super().__init__(f"Path is not absolute: {path}")
6 changes: 4 additions & 2 deletions src/pypendency/loaders/loader.py
Expand Up @@ -6,10 +6,12 @@

class Loader(ABC):
@abstractmethod
def load(self, resource: str) -> None: pass
def load(self, resource: str) -> None:
pass

@abstractmethod
def load_dir(self, directory: str) -> None: pass
def load_dir(self, directory: str) -> None:
pass

def _guard_path_is_absolute(self, path: str) -> None:
if not os.path.isabs(path):
Expand Down
4 changes: 2 additions & 2 deletions src/pypendency/loaders/py_loader.py
Expand Up @@ -26,7 +26,7 @@ def __load_by_absolute_path(self, resource: str) -> None:

module = module_from_spec(spec)
spec.loader.exec_module(module)
self.__load_module(resource, module)
self.__load_module(resource, module) # type: ignore

def __load_module(self, resource: str, module: PythonLoadableModuleType) -> None:
try:
Expand All @@ -40,7 +40,7 @@ def load_by_module_name(self, resource: str) -> None:
if package is None:
raise exceptions.ResourceNotFound(resource)

self.__load_module(resource, package)
self.__load_module(resource, package) # type: ignore

def load_dir(self, directory: str) -> None:
self._guard_path_is_absolute(directory)
Expand Down
18 changes: 7 additions & 11 deletions src/pypendency/loaders/yaml_loader.py
Expand Up @@ -26,38 +26,34 @@ def __load_by_absolute_path(self, resource: str) -> None:
raise exceptions.ParsingErrorOnResource(resource) from e

for identifier, definition_content in resource_loaded.items():
arguments = [
Argument.no_kw_argument(arg)
for arg in definition_content.get('args', [])
]
arguments = [Argument.no_kw_argument(arg) for arg in definition_content.get("args", [])]

arguments += [
Argument(arg_name, arg_value)
for arg_name, arg_value in definition_content.get('kwargs', {}).items()
Argument(arg_name, arg_value) for arg_name, arg_value in definition_content.get("kwargs", {}).items()
]

tags = {
Tag(identifier=identifier, value=value)
for identifier, value in definition_content.get('tags', {}).items()
for identifier, value in definition_content.get("tags", {}).items()
}

self.__container.set_definition(
Definition(
identifier,
definition_content['fqn'],
definition_content["fqn"],
arguments,
tags,
)
)

def __resource_loaded(self, resource: str) -> dict:
with open(resource, 'r') as stream:
with open(resource, "r") as stream:
return yaml.safe_load(stream)

def load_dir(self, directory: str) -> None:
self._guard_path_is_absolute(directory)
files = glob.glob(f'{directory}/**/[!_]*.yml', recursive=True)
files.extend(glob.glob(f'{directory}/**/[!_]*.yaml', recursive=True))
files = glob.glob(f"{directory}/**/[!_]*.yml", recursive=True)
files.extend(glob.glob(f"{directory}/**/[!_]*.yaml", recursive=True))

for file in files:
self.__load_by_absolute_path(file)
2 changes: 1 addition & 1 deletion src/pypendency/tag.py
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Dict, List, Optional, ClassVar
from typing import Optional, ClassVar


@dataclass(frozen=True)
Expand Down
3 changes: 2 additions & 1 deletion src/pypendency/types/python_loadable_module.py
Expand Up @@ -4,4 +4,5 @@


class PythonLoadableModuleType(ModuleType):
def load(self, container_builder: ContainerBuilder) -> None: pass
def load(self, container_builder: ContainerBuilder) -> None:
pass
5 changes: 1 addition & 4 deletions tests/loaders/test_yaml_loader.py
Expand Up @@ -39,10 +39,7 @@ def test_load_does_nothing_for_empty_yaml(self):
def test_load_works_as_expected(self):
path = os.path.join(self.current_dir, "..", "resources", "test_di.yaml")
self.loader.load(path)
self.assertIsInstance(
self._container_builder.get("example.C"),
C
)
self.assertIsInstance(self._container_builder.get("example.C"), C)
self.assertEqual(
self._container_builder.get_service_tags("example.tagged_A"),
{
Expand Down
4 changes: 1 addition & 3 deletions tests/resources/loaders/a/c/two_levels_file.py
Expand Up @@ -3,6 +3,4 @@


def load(container_builder: ContainerBuilder):
container_builder.set_definition(
Definition('two_levels_file', 'tests.resources.class_a.A')
)
container_builder.set_definition(Definition("two_levels_file", "tests.resources.class_a.A"))
4 changes: 1 addition & 3 deletions tests/resources/loaders/a/one_level_file.py
Expand Up @@ -3,6 +3,4 @@


def load(container_builder: ContainerBuilder):
container_builder.set_definition(
Definition('one_level_file', 'tests.resources.class_a.A')
)
container_builder.set_definition(Definition("one_level_file", "tests.resources.class_a.A"))
4 changes: 1 addition & 3 deletions tests/resources/loaders/same_level_file.py
Expand Up @@ -3,6 +3,4 @@


def load(container_builder: ContainerBuilder):
container_builder.set_definition(
Definition('same_level_file', 'tests.resources.class_a.A')
)
container_builder.set_definition(Definition("same_level_file", "tests.resources.class_a.A"))

0 comments on commit 7b71bb9

Please sign in to comment.