diff --git a/README.txt b/README.txt index 9ccfe37..b6dc9eb 100644 --- a/README.txt +++ b/README.txt @@ -1,31 +1,31 @@ -I was too lazy to write this in a markdown file. And lazy enough to write this in English as well. ------------------------------------------------- - -Welp. Easy RESTful API and stuff. - -Endpoints: -- GET | / | Sample root endpoint. -- GET | /docs | Swagger UI for testing the endpoints. -- GET | /users | List all users. -- GET | /users/{id} | Gets a user by its id in database. -- POST | /users | Adds a new user to the database. - -Dependencies JIC you want to make your own virtual python environment: -- uvicorn | To run API apps -- fastapi | This project's API Framework -- pydantic | Data validation library -- sqlalchemy | Framework to operate databases with abstractions - -Extra implementations: -- Database | SQLite3 Database file. /src/users.db -- Windows & UNIX| For Windows or Linux servers, this API includes both .env directories for windows and UNIX. /.env & /.env-unix - -Tested on: -- Windows -- Linux (WSL & Native) - Using Arch Linux. - -Not tested on: -- BSD - FreeBSD and/or NetBSD. -- Docker. -- Micropython - ESP32. +I was too lazy to write this in a markdown file. And lazy enough to write this in English as well. +------------------------------------------------ + +Welp. Easy RESTful API and stuff. + +Endpoints: +- GET | / | Sample root endpoint. +- GET | /docs | Swagger UI for testing the endpoints. +- GET | /users | List all users. +- GET | /users/{id} | Gets a user by its id in database. +- POST | /users | Adds a new user to the database. + +Dependencies JIC you want to make your own virtual python environment: +- uvicorn | To run API apps +- fastapi | This project's API Framework +- pydantic | Data validation library +- sqlalchemy | Framework to operate databases with abstractions + +Extra implementations: +- Database | SQLite3 Database file. /src/users.db +- Windows & UNIX| For Windows or Linux servers, this API includes both .env directories for windows and UNIX. /.env & /.env-unix + +Tested on: +- Windows +- Linux (WSL & Native) - Using Arch Linux. + +Not tested on: +- BSD - FreeBSD and/or NetBSD. +- Docker. +- Micropython - ESP32. - Other Python compatible systems. \ No newline at end of file diff --git a/deps.json b/deps.json index 706c6ee..8b82685 100644 --- a/deps.json +++ b/deps.json @@ -1,8 +1,8 @@ -{ - "dependencies": { - "1" : "uviconrn", - "2" : "fastapi", - "3" : "pydantic", - "4" : "sqlalchemy" - } +{ + "dependencies": { + "1" : "uviconrn", + "2" : "fastapi", + "3" : "pydantic", + "4" : "sqlalchemy" + } } \ No newline at end of file diff --git a/src/application/exceptions.py b/src/application/exceptions.py index fbbae07..5c6014b 100644 --- a/src/application/exceptions.py +++ b/src/application/exceptions.py @@ -1,2 +1,2 @@ -class UserAlreadyExists(Exception): - pass +class UserAlreadyExists(Exception): + pass diff --git a/src/application/ports/user_repository.py b/src/application/ports/user_repository.py index 00d9697..173423e 100644 --- a/src/application/ports/user_repository.py +++ b/src/application/ports/user_repository.py @@ -1,13 +1,13 @@ -from abc import ABC, abstractmethod -from domain.users import User - -class UserRepository(ABC): - @abstractmethod - def save(self, user: User): - ... - @abstractmethod - def viewAll(self) -> list[User]: - ... - @abstractmethod - def viewById(self, user_id:int): +from abc import ABC, abstractmethod +from domain.users import User + +class UserRepository(ABC): + @abstractmethod + def save(self, user: User): + ... + @abstractmethod + def viewAll(self) -> list[User]: + ... + @abstractmethod + def viewById(self, user_id:int): ... \ No newline at end of file diff --git a/src/application/services/user_services.py b/src/application/services/user_services.py index 96d5a2d..338d828 100644 --- a/src/application/services/user_services.py +++ b/src/application/services/user_services.py @@ -1,33 +1,33 @@ -from domain.users import User -from application.ports.user_repository import UserRepository - -class CreateUser: - def __init__(self, repo: UserRepository): - if not isinstance(repo, UserRepository): - raise TypeError("repo must implement UserRepository") - self.repo = repo - - def execute(self, name:str, email:str, phone:str): - user = User(user_id=0, email=email, name=name, phone=phone) - self.repo.save(user) - return user - -class ViewUsers: - def __init__(self, repo: UserRepository): - if not isinstance(repo, UserRepository): - raise TypeError("repo must implement UserRepository") - self.repo = repo - - def execute(self): - users = self.repo.viewAll() - return users - -class ViewUserById: - def __init__(self, repo: UserRepository): - if not isinstance(repo, UserRepository): - raise TypeError("repo must implement UserRepository") - self.repo = repo - - def execute(self, user_id: int) -> User | None: - user = self.repo.viewById(user_id) +from domain.users import User +from application.ports.user_repository import UserRepository + +class CreateUser: + def __init__(self, repo: UserRepository): + if not isinstance(repo, UserRepository): + raise TypeError("repo must implement UserRepository") + self.repo = repo + + def execute(self, name:str, email:str, phone:str): + user = User(user_id=0, email=email, name=name, phone=phone) + self.repo.save(user) + return user + +class ViewUsers: + def __init__(self, repo: UserRepository): + if not isinstance(repo, UserRepository): + raise TypeError("repo must implement UserRepository") + self.repo = repo + + def execute(self): + users = self.repo.viewAll() + return users + +class ViewUserById: + def __init__(self, repo: UserRepository): + if not isinstance(repo, UserRepository): + raise TypeError("repo must implement UserRepository") + self.repo = repo + + def execute(self, user_id: int) -> User | None: + user = self.repo.viewById(user_id) return user \ No newline at end of file diff --git a/src/domain/users.py b/src/domain/users.py index 414095c..41aeb6e 100644 --- a/src/domain/users.py +++ b/src/domain/users.py @@ -1,8 +1,8 @@ -from dataclasses import dataclass - -@dataclass -class User: - user_id: int - name: str - email: str +from dataclasses import dataclass + +@dataclass +class User: + user_id: int + name: str + email: str phone: str \ No newline at end of file diff --git a/src/infrastructure/adapters/persistence/__init__.py b/src/infrastructure/adapters/persistence/__init__.py index b2c589b..af1f72a 100644 --- a/src/infrastructure/adapters/persistence/__init__.py +++ b/src/infrastructure/adapters/persistence/__init__.py @@ -1,5 +1,5 @@ - -from infrastructure.adapters.persistence.db import engine -from infrastructure.adapters.persistence.models import Base - -Base.metadata.create_all(bind=engine) + +from infrastructure.adapters.persistence.db import engine +from infrastructure.adapters.persistence.models import Base + +Base.metadata.create_all(bind=engine) diff --git a/src/infrastructure/adapters/persistence/db.py b/src/infrastructure/adapters/persistence/db.py index 1ae0eba..c61c6f9 100644 --- a/src/infrastructure/adapters/persistence/db.py +++ b/src/infrastructure/adapters/persistence/db.py @@ -1,17 +1,17 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, declarative_base - -DATABASE_URL = "sqlite:///./users.db" - -engine = create_engine( - DATABASE_URL, - connect_args={"check_same_thread": False} - ) - -SessionLocal = sessionmaker( - bind=engine, - autocommit=False, - autoflush=False - ) - +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +DATABASE_URL = "sqlite:///./users.db" + +engine = create_engine( + DATABASE_URL, + connect_args={"check_same_thread": False} + ) + +SessionLocal = sessionmaker( + bind=engine, + autocommit=False, + autoflush=False + ) + Base = declarative_base() \ No newline at end of file diff --git a/src/infrastructure/adapters/persistence/models.py b/src/infrastructure/adapters/persistence/models.py index c6e25e5..3cc098b 100644 --- a/src/infrastructure/adapters/persistence/models.py +++ b/src/infrastructure/adapters/persistence/models.py @@ -1,10 +1,10 @@ -from sqlalchemy import Column, Integer, String -from infrastructure.adapters.persistence.db import Base - -class UserModel(Base): - __tablename__ = "users" - - user_id = Column(Integer, primary_key=True, index=True) - name = Column(String, unique=False, index=True) - email = Column(String, unique=True, index=True) +from sqlalchemy import Column, Integer, String +from infrastructure.adapters.persistence.db import Base + +class UserModel(Base): + __tablename__ = "users" + + user_id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=False, index=True) + email = Column(String, unique=True, index=True) phone = Column(String, unique=True, index=True) \ No newline at end of file diff --git a/src/infrastructure/adapters/persistence/user_repository_sql.py b/src/infrastructure/adapters/persistence/user_repository_sql.py index eebf219..d659514 100644 --- a/src/infrastructure/adapters/persistence/user_repository_sql.py +++ b/src/infrastructure/adapters/persistence/user_repository_sql.py @@ -1,61 +1,61 @@ -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from domain.users import User -from application.ports.user_repository import UserRepository -from infrastructure.adapters.persistence.db import SessionLocal -from infrastructure.adapters.persistence.models import UserModel -from application.exceptions import UserAlreadyExists - -class SqlUserRepository(UserRepository): - def __init__(self): - self.db: Session = SessionLocal() - - def save(self, user: User): - model = UserModel( - name= user.name, - email=user.email, - phone=user.phone - ) - - self.db.add(model) - - try: - self.db.commit() - except IntegrityError: - self.db.rollback() - raise UserAlreadyExists("A user with the same phone number or name does already exist.") - - - self.db.refresh(model) - - return User( - user_id = model.user_id, - name = model.name, - email = model.email, - phone = model.phone - ) - - def viewAll(self) -> list[User]: - models = self.db.query(UserModel).all() - - return [ - User( - user_id=model.user_id, - name = model.name, - email = model.email, - phone = model.phone - ) for model in models - ] - - def viewById(self, user_id: int) -> User | None: - model = self.db.query(UserModel).filter(UserModel.user_id == user_id).first() - - if not model: - return None - - return User( - user_id=model.user_id, - name = model.name, - email = model.email, - phone = model.phone +from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError +from domain.users import User +from application.ports.user_repository import UserRepository +from infrastructure.adapters.persistence.db import SessionLocal +from infrastructure.adapters.persistence.models import UserModel +from application.exceptions import UserAlreadyExists + +class SqlUserRepository(UserRepository): + def __init__(self): + self.db: Session = SessionLocal() + + def save(self, user: User): + model = UserModel( + name= user.name, + email=user.email, + phone=user.phone + ) + + self.db.add(model) + + try: + self.db.commit() + except IntegrityError: + self.db.rollback() + raise UserAlreadyExists("A user with the same phone number or name does already exist.") + + + self.db.refresh(model) + + return User( + user_id = model.user_id, + name = model.name, + email = model.email, + phone = model.phone + ) + + def viewAll(self) -> list[User]: + models = self.db.query(UserModel).all() + + return [ + User( + user_id=model.user_id, + name = model.name, + email = model.email, + phone = model.phone + ) for model in models + ] + + def viewById(self, user_id: int) -> User | None: + model = self.db.query(UserModel).filter(UserModel.user_id == user_id).first() + + if not model: + return None + + return User( + user_id=model.user_id, + name = model.name, + email = model.email, + phone = model.phone ) \ No newline at end of file diff --git a/src/infrastructure/api/users/app.py b/src/infrastructure/api/users/app.py index dda1a85..020143c 100644 --- a/src/infrastructure/api/users/app.py +++ b/src/infrastructure/api/users/app.py @@ -1,7 +1,7 @@ -from fastapi import FastAPI -from infrastructure.api.users.router import router - -def create_app() -> FastAPI: - app = FastAPI(title="Users service") - app.include_router(router) +from fastapi import FastAPI +from infrastructure.api.users.router import router + +def create_app() -> FastAPI: + app = FastAPI(title="Users service") + app.include_router(router) return app \ No newline at end of file diff --git a/src/infrastructure/api/users/root.py b/src/infrastructure/api/users/root.py index ceefcae..bf8b1bc 100644 --- a/src/infrastructure/api/users/root.py +++ b/src/infrastructure/api/users/root.py @@ -1,10 +1,10 @@ -from fastapi import APIRouter - -router = APIRouter() - -@router.get("/") -def root(): - return { - "Status": "Running", - "Docs": "/docs" +from fastapi import APIRouter + +router = APIRouter() + +@router.get("/") +def root(): + return { + "Status": "Running", + "Docs": "/docs" } \ No newline at end of file diff --git a/src/infrastructure/api/users/router.py b/src/infrastructure/api/users/router.py index 5cd7e96..aa4fb16 100644 --- a/src/infrastructure/api/users/router.py +++ b/src/infrastructure/api/users/router.py @@ -1,17 +1,17 @@ -from fastapi import APIRouter -from infrastructure.api.users.users import router as users_router -from infrastructure.api.users.root import router as root_router - -router = APIRouter() - -router.include_router( - users_router, - prefix="/users", - tags=["users"] - ) - -router.include_router( - root_router, - prefix='', - tags=["root"] +from fastapi import APIRouter +from infrastructure.api.users.users import router as users_router +from infrastructure.api.users.root import router as root_router + +router = APIRouter() + +router.include_router( + users_router, + prefix="/users", + tags=["users"] + ) + +router.include_router( + root_router, + prefix='', + tags=["root"] ) \ No newline at end of file diff --git a/src/infrastructure/api/users/users.py b/src/infrastructure/api/users/users.py index 5c9380a..463a353 100644 --- a/src/infrastructure/api/users/users.py +++ b/src/infrastructure/api/users/users.py @@ -1,44 +1,44 @@ -from fastapi import APIRouter, HTTPException -from application.services.user_services import CreateUser, ViewUsers, ViewUserById -from infrastructure.adapters.persistence.user_repository_sql import SqlUserRepository -from application.exceptions import UserAlreadyExists - -router = APIRouter() - -@router.post("/") -def create_user(name:str, email:str, phone:str): - service = CreateUser(SqlUserRepository()) - - try: - return service.execute(name,email,phone) - except UserAlreadyExists as e: - raise HTTPException ( - status_code=409, - detail=str(e) - ) - - -@router.get("/") -def view_all_users(): - - service = ViewUsers(SqlUserRepository()) - - try: - return service.execute() - except Exception as e: - raise HTTPException( - status_code=500, - detail=e) - -@router.get("/{id}") -def view_user_by_id(user_id: int): - service = ViewUserById(SqlUserRepository()) - - result = service.execute(user_id) - - if result is None: - raise HTTPException( - status_code=404, - detail="User not found") - +from fastapi import APIRouter, HTTPException +from application.services.user_services import CreateUser, ViewUsers, ViewUserById +from infrastructure.adapters.persistence.user_repository_sql import SqlUserRepository +from application.exceptions import UserAlreadyExists + +router = APIRouter() + +@router.post("/") +def create_user(name:str, email:str, phone:str): + service = CreateUser(SqlUserRepository()) + + try: + return service.execute(name,email,phone) + except UserAlreadyExists as e: + raise HTTPException ( + status_code=409, + detail=str(e) + ) + + +@router.get("/") +def view_all_users(): + + service = ViewUsers(SqlUserRepository()) + + try: + return service.execute() + except Exception as e: + raise HTTPException( + status_code=500, + detail=e) + +@router.get("/{id}") +def view_user_by_id(user_id: int): + service = ViewUserById(SqlUserRepository()) + + result = service.execute(user_id) + + if result is None: + raise HTTPException( + status_code=404, + detail="User not found") + return result \ No newline at end of file diff --git a/src/main.py b/src/main.py index 2173672..a3b7108 100644 --- a/src/main.py +++ b/src/main.py @@ -1,16 +1,16 @@ -from infrastructure.api.users.app import create_app - -app = create_app() - -def run(): - import uvicorn - uvicorn.run( - "main:app", - host="0.0.0.0", - port=8000, - reload=True, - log_level="info", - ) - -if __name__ == "__main__": - run() +from infrastructure.api.users.app import create_app + +app = create_app() + +def run(): + import uvicorn + uvicorn.run( + "main:app", + host="0.0.0.0", + port=8000, + reload=True, + log_level="info", + ) + +if __name__ == "__main__": + run()