diff --git a/.gitignore b/.gitignore index 485dee6..53e4fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +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 + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + .idea diff --git a/solid/__pycache__/__init__.cpython-310.pyc b/solid/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..ad3477d Binary files /dev/null and b/solid/__pycache__/__init__.cpython-310.pyc differ diff --git a/solid/dependency_inversion/__init__.py b/solid/dependency_inversion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solid/dependency_inversion/file_reader.py b/solid/dependency_inversion/file_reader.py new file mode 100644 index 0000000..d440cad --- /dev/null +++ b/solid/dependency_inversion/file_reader.py @@ -0,0 +1,40 @@ +import json +from pathlib import Path + + +class Reader: + + def read_lines(self) -> list[str]: + raise Exception("not yet implemented") + + +class FileReader(Reader): + + def __init__(self, path: Path|str): + self._path = Path(path) + + def read(self) -> str: + return self._path.read_text('utf-8') + + def read_lines(self) -> list[str]: + return [l for l in self.read().split('\n') if l.strip()] + + +class User: + def __init__(self, name: str): + self.name = name + + @classmethod + def from_json(cls, blob) -> 'User' | None: + if 'name' not in blob: + return + return cls(blob['name']) + + +class UserLoader: + def __init__(self, reader: Reader): + self._file_reader = reader + + def get_users(self) -> list[User]: + ret = [User.from_json(json.loads(line)) for line in self._file_reader.read_lines()] + return [r for r in ret if r is not None] diff --git a/solid/interface_segregations/__init__.py b/solid/interface_segregations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solid/liskov_substitution/__init__.py b/solid/liskov_substitution/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solid/liskov_substitution/users.py b/solid/liskov_substitution/users.py new file mode 100644 index 0000000..a56d869 --- /dev/null +++ b/solid/liskov_substitution/users.py @@ -0,0 +1,34 @@ +from datetime import datetime + + +class User: + def __init__(self, name: str, password: str): + self.name = name + self._password = password + + def is_right_password(self, password) -> bool: + # ne JAMAIS faire cela + return password == self._password + + +class Admin(User): + def __init__(self, name: str, password: str, one_time_password: str): + super(name, password) + self._one_time_password = one_time_password + + def is_right_password(self, password, otp_challange) -> bool: + is_otp_valid = self._one_time_password == otp_challange + return super().is_right_password(password) and is_otp_valid + + +class LoggedInUser: + def __init__(self, user: User, logged_in_time: datetime): + self.user = user + self.logged_in_time = logged_in_time + + +def loging(user: User, password: str) -> LoggedInUser | None: + if not user.is_right_password(password): + return None + + return LoggedInUser(user, datetime.utcnow()) \ No newline at end of file diff --git a/solid/open_close/__init__.py b/solid/open_close/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solid/open_close/users.py b/solid/open_close/users.py new file mode 100644 index 0000000..f86ba46 --- /dev/null +++ b/solid/open_close/users.py @@ -0,0 +1,45 @@ +import sqlite3 + + +class User: + def __init__(self, name: str): + self.name = name + + +class UserStorage: + def __init__(self): + self._memory: list[User] = [] + + def save(self, user: User) -> None: + self._memory.append(user) + + def get(self, name: str) -> User | None: + for user in self._memory: + if user.name == name: + return user + + def get_if_name_starts_by_a(self, name: str) -> User | None: + if name.lower().startswith('a'): + return self.get(name) + + +class SQliteUserStorage(UserStorage): + def __init__(self, conn: sqlite3.Connection): + self._conn = conn + + @classmethod + def create(cls, url: str) -> 'SQliteUserStorage': + conn = sqlite3.connect(url) + conn.execute('''CREATE TABLE IF NOT EXISTS users(name TEXT UNIQUE NOT NULL)''') + return cls(conn) + + def save(self, user: User) -> None: + self._conn.execute( + '''INSERT INTO users(name) VALUES (?)''', (user.name, ) + ) + + def get(self, name: str) -> User | None: + res = self._conn.execute("""SELECT name FROM users WHERE name = ?""", (name, )).fetchone() + if res is None: + return None + return User(res[0]) diff --git a/solid/open_close/users_test.py b/solid/open_close/users_test.py new file mode 100644 index 0000000..9acb9b8 --- /dev/null +++ b/solid/open_close/users_test.py @@ -0,0 +1,40 @@ +from solid.open_close.users import UserStorage, User, SQliteUserStorage + + +class TestUserStorage: + def test_happy_path(self): + storage = UserStorage() + storage.save(User('Eliotte')) + assert storage.get('Eliotte') is not None + + def test_not_found(self): + storage = UserStorage() + assert storage.get('Eliotte') is None + + def test_get_if_name_starts_by_a(self): + storage = UserStorage() + storage.save(User('Eliotte')) + storage.save(User('Andre')) + + assert storage.get_if_name_starts_by_a('Eliotte') is None + assert storage.get_if_name_starts_by_a('Andre') is not None + + +class TestSQliteUserStorage: + def test_happy_path(self): + storage = SQliteUserStorage.create(":memory:") + storage.save(User('Eliotte')) + assert storage.get('Eliotte') is not None + + def test_not_found(self): + storage = SQliteUserStorage.create(":memory:") + assert storage.get('Eliotte') is None + + def test_get_if_name_starts_by_a(self): + storage = SQliteUserStorage.create(":memory:") + storage.save(User('Eliotte')) + storage.save(User('Andre')) + + assert storage.get_if_name_starts_by_a('Eliotte') is None + assert storage.get_if_name_starts_by_a('Andre') is not None +