From 727fd6abb921199f187dbe9bffdf68eb4002174b Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Fri, 1 Mar 2024 19:43:31 -0500 Subject: [PATCH 1/5] intialize backed project + starter code --- .gitignore | 2 + .vscode/extensions.json | 15 ++++++ .vscode/settings.json | 22 ++++++++ README.md | 80 +++++++++++++++++++++++++++- backend/__init__.py | 0 backend/database.py | 29 ++++++++++ backend/entities/__init__.py | 0 backend/env.py | 21 ++++++++ backend/main.py | 0 backend/models/__init__.py | 0 backend/requirements.txt | 6 +++ backend/services/__init__.py | 0 backend/test/__init__.py | 0 backend/test/services/__init__.py | 0 backend/test/services/conftest.py | 24 +++++++++ backend/test/services/sample_test.py | 9 ++++ docker-compose.yml | 25 +++++++++ 17 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 backend/__init__.py create mode 100644 backend/database.py create mode 100644 backend/entities/__init__.py create mode 100644 backend/env.py create mode 100644 backend/main.py create mode 100644 backend/models/__init__.py create mode 100644 backend/requirements.txt create mode 100644 backend/services/__init__.py create mode 100644 backend/test/__init__.py create mode 100644 backend/test/services/__init__.py create mode 100644 backend/test/services/conftest.py create mode 100644 backend/test/services/sample_test.py create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c303a94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/backend/.env +__pycache__ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ec0d050 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,15 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "ecmel.vscode-html-css", + "ms-vscode.vscode-typescript-next", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "vscode-icons-team.vscode-icons", + "tamasfe.even-better-toml", + "ckolkman.vscode-postgres", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.autopep8" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c85959b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[python]": { + "editor.defaultFormatter": "ms-python.autopep8" + }, + "python.analysis.extraPaths": ["/backend/"], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingParameterType": "error", + "reportGeneralTypeIssues": "error", + "reportDeprecated": "error", + "reportImportCycles": "error" + } +} diff --git a/README.md b/README.md index 8cc65fc..ac920f3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # 🧭 Compass Center's Internal Resource Management App ## 🛠 Technologies + - Next.js - TailwindCSS - TypeScript - PostgreSQL ## 📁 File Setup + ``` \compass \components // Components organized in folders related to specific pages @@ -18,9 +20,11 @@ ``` ## 🚀 To Start + Follow these steps to set up your local environment: + ``` -\\ Clone this repository +\\ Clone this repository git clone https://github.com/cssgunc/compass.git \\ Go into main folder cd compass @@ -30,6 +34,78 @@ npm install npm run dev ``` +Also add following variables inside of a .env file inside of the backend directory + +``` +\\ .env file contents + +POSTGRES_DB=compass +POSTGRES_USER=postgres +POSTGRES_PASSWORD=admin +POSTGRES_HOST=db +POSTGRES_PORT=5432 +HOST=localhost +``` + +Install necessary python packages + +``` +\\ Change directory from compass into backend directory +cd backend + +\\ Install python dependencies +pip3 install -r requirements.txt +``` + +## Backend Starter + +Follow these steps to start up Postgres database: + +Make sure you have Docker installed! + +``` + +\\ Spins up local postgres database and pgadmin +docker-compose up -d + +\\ Stop and teardown containers +docker-compose down + +\\ Stop and teardown containers + volumes (full reset) +docker-compose down -v +``` + +### Accesing pgAdmin 4 + +- First go to http://localhost:5050/ on your browser +- Log in using the credentials admin@example.com and admin +- Click **Add New Server** +- Fill in the name field with Compass (can be anything) +- Click **Connection** tab and fill in the following: + - Host name/address: db + - Maintence database: compass + - Username: postgres + - Password: admin +- Click **Save** at the bottom to add connection +- Click **Server** dropdown on the left and click through items inside the **Compass** server + +## Testing Backend Code + +- Write tests for any service you create and any function in those services +- Make sure to add docstrings detailing what the file is doing and what each test is doing +- Name all test functions with test\_[testContent]() +- Utitlize dependency injection for commonly used services + +``` +\\ Run all tests by being in the backend directory +pytest + +\\ Run specific tests by passing in file as a parameter +pytest [fileName] +``` + ## 💡 Dev Notes + - For each task, create a branch in the format '[your name]-[ticket number]-[task description]' -- Only commit your work to that branch and then make a git request to '/main' \ No newline at end of file +- Only commit your work to that branch and then make a git request to '/main' +- When creating new files in the backend and code is in python make sure to add a docstring for the file and any function you create ("""[content]"""") diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..4c5ced7 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,29 @@ +"""SQLAlchemy DB Engine and Session niceties for FastAPI dependency injection.""" + +import sqlalchemy +from sqlalchemy.orm import Session +from .env import getenv + + +def _engine_str(database: str = getenv("POSTGRES_DB")) -> str: + """Helper function for reading settings from environment variables to produce connection string.""" + dialect = "postgresql+psycopg2" + user = getenv("POSTGRES_USER") + password = getenv("POSTGRES_PASSWORD") + host = getenv("POSTGRES_HOST") + port = getenv("POSTGRES_PORT") + return f"{dialect}://{user}:{password}@{host}:{port}/{database}" + + +engine = sqlalchemy.create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + + +def db_session(): + """Generator function offering dependency injection of SQLAlchemy Sessions.""" + print("ran") + session = Session(engine) + try: + yield session + finally: + session.close() diff --git a/backend/entities/__init__.py b/backend/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/env.py b/backend/env.py new file mode 100644 index 0000000..356efb7 --- /dev/null +++ b/backend/env.py @@ -0,0 +1,21 @@ +"""Load environment variables from a .env file or the process' environment.""" + +import os +import dotenv + +# Load envirnment variables from .env file upon module start. +dotenv.load_dotenv(f"{os.path.dirname(__file__)}/.env", verbose=True) + + +def getenv(variable: str) -> str: + """Get value of environment variable or raise an error if undefined. + + Unlike `os.getenv`, our application expects all environment variables it needs to be defined + and we intentionally fast error out with a diagnostic message to avoid scenarios of running + the application when expected environment variables are not set. + """ + value = os.getenv(variable) + if value is not None: + return value + else: + raise NameError(f"Error: {variable} Environment Variable not Defined") diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..5c532a5 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,6 @@ +fastapi[all] >=0.100.0, <0.101.0 +sqlalchemy >=2.0.4, <2.1.0 +psycopg2 >=2.9.5, <2.10.0 +alembic >=1.10.2, <1.11.0 +pytest >=7.2.1, <7.3.0 +python-dotenv >=1.0.0, <1.1.0 \ No newline at end of file diff --git a/backend/services/__init__.py b/backend/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/test/__init__.py b/backend/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/test/services/__init__.py b/backend/test/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/test/services/conftest.py b/backend/test/services/conftest.py new file mode 100644 index 0000000..c06a731 --- /dev/null +++ b/backend/test/services/conftest.py @@ -0,0 +1,24 @@ +"""Shared pytest fixtures for database dependent tests.""" + +import pytest +from sqlalchemy import Engine +from sqlalchemy.orm import Session + +from ...database import db_session + + +@pytest.fixture(scope="session") +def test_engine() -> Engine: + session = db_session() + return session + + +@pytest.fixture(scope="function") +def session(test_engine: Engine): + # entities.EntityBase.metadata.drop_all(test_engine) + # entities.EntityBase.metadata.create_all(test_engine) + session = Session(test_engine) + try: + yield session + finally: + session.close() diff --git a/backend/test/services/sample_test.py b/backend/test/services/sample_test.py new file mode 100644 index 0000000..7d4cfbd --- /dev/null +++ b/backend/test/services/sample_test.py @@ -0,0 +1,9 @@ +"""Sample Test File""" + +import pytest +from sqlalchemy import Engine + + +def test_sample(session: Engine): + print(session) + assert session != None diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f2cabf8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3" + +services: + db: + image: "postgres:latest" + ports: + - "5432:5432" + env_file: + - ./backend/.env + volumes: + - compass-center-postgres:/var/lib/postgresql/data + # - ./backend/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + + pgadmin: + image: dpage/pgadmin4:latest + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - "5050:80" + depends_on: + - db + +volumes: + compass-center-postgres: From 7e1e31244c3697ff18083533c857ea6f6c45cb2c Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Sat, 2 Mar 2024 15:16:28 -0500 Subject: [PATCH 2/5] second commit --- .vscode/extensions.json | 2 +- .vscode/settings.json | 6 +++-- backend/database.py | 2 +- backend/entities/__init__.py | 2 ++ backend/entities/entity_base.py | 12 +++++++++ backend/entities/sample_entity.py | 12 +++++++++ backend/script/__init__.py | 0 backend/script/reset_demo.py | 38 ++++++++++++++++++++++++++++ backend/test/models/__init__.py | 0 backend/test/services/conftest.py | 4 +-- backend/test/services/sample_test.py | 4 +++ 11 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 backend/entities/entity_base.py create mode 100644 backend/entities/sample_entity.py create mode 100644 backend/script/__init__.py create mode 100644 backend/script/reset_demo.py create mode 100644 backend/test/models/__init__.py diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ec0d050..11dae3a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,6 +10,6 @@ "ckolkman.vscode-postgres", "ms-python.python", "ms-python.vscode-pylance", - "ms-python.autopep8" + "ms-python.black-formatter" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index c85959b..3cf96ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "[python]": { "editor.defaultFormatter": "ms-python.autopep8" }, - "python.analysis.extraPaths": ["/backend/"], + "python.analysis.extraPaths": ["backend/"], "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, "python.analysis.diagnosticSeverityOverrides": { @@ -18,5 +18,7 @@ "reportGeneralTypeIssues": "error", "reportDeprecated": "error", "reportImportCycles": "error" - } + }, + "python.analysis.autoImportCompletions": false, + "python.analysis.typeCheckingMode": "off" } diff --git a/backend/database.py b/backend/database.py index 4c5ced7..b53d444 100644 --- a/backend/database.py +++ b/backend/database.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session from .env import getenv -def _engine_str(database: str = getenv("POSTGRES_DB")) -> str: +def _engine_str(database: str = getenv("POSTGRES_DATABASE")) -> str: """Helper function for reading settings from environment variables to produce connection string.""" dialect = "postgresql+psycopg2" user = getenv("POSTGRES_USER") diff --git a/backend/entities/__init__.py b/backend/entities/__init__.py index e69de29..39abb6f 100644 --- a/backend/entities/__init__.py +++ b/backend/entities/__init__.py @@ -0,0 +1,2 @@ +from .entity_base import EntityBase +from .sample_entity import SampleEntity diff --git a/backend/entities/entity_base.py b/backend/entities/entity_base.py new file mode 100644 index 0000000..5e34685 --- /dev/null +++ b/backend/entities/entity_base.py @@ -0,0 +1,12 @@ +"""Abstract superclass of all entities in the application. + +There is no reason to instantiate this class directly. Instead, look toward the child classes. +Additionally, import from the top-level entities file which indexes all entity implementations. +""" + + +from sqlalchemy.orm import DeclarativeBase + + +class EntityBase(DeclarativeBase): + pass diff --git a/backend/entities/sample_entity.py b/backend/entities/sample_entity.py new file mode 100644 index 0000000..28948ff --- /dev/null +++ b/backend/entities/sample_entity.py @@ -0,0 +1,12 @@ +from sqlalchemy import create_engine, Column, Integer, String +from sqlalchemy.orm import Mapped, mapped_column, relationship +from .entity_base import EntityBase + + +class SampleEntity(EntityBase): + __tablename__ = 'persons' + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + name: Mapped[str] = mapped_column(String, nullable=False) + age: Mapped[int] = mapped_column(Integer) + email: Mapped[str] = mapped_column(String, unique=True, nullable=False) diff --git a/backend/script/__init__.py b/backend/script/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/script/reset_demo.py b/backend/script/reset_demo.py new file mode 100644 index 0000000..65de426 --- /dev/null +++ b/backend/script/reset_demo.py @@ -0,0 +1,38 @@ +from sqlalchemy import text, create_engine +from ..database import engine +from ..env import getenv +from .. import entities + +database = getenv("POSTGRES_DATABASE") + + +def _engine_str() -> str: + """Helper function for reading settings from environment variables to produce connection string.""" + dialect = "postgresql+psycopg2" + user = getenv("POSTGRES_USER") + password = getenv("POSTGRES_PASSWORD") + host = getenv("POSTGRES_HOST") + port = getenv("POSTGRES_PORT") + return f"{dialect}://{user}:{password}@{host}:{port}" + + +engine = create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + + +with engine.connect() as connection: + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"DROP DATABASE IF EXISTS {database}") + connection.execute(stmt) + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"CREATE DATABASE {database}") + connection.execute(stmt) + +entities.EntityBase.metadata.drop_all(engine) +entities.EntityBase.metadata.create_all(engine) diff --git a/backend/test/models/__init__.py b/backend/test/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/test/services/conftest.py b/backend/test/services/conftest.py index c06a731..42b2fdd 100644 --- a/backend/test/services/conftest.py +++ b/backend/test/services/conftest.py @@ -3,20 +3,20 @@ import pytest from sqlalchemy import Engine from sqlalchemy.orm import Session +import subprocess from ...database import db_session @pytest.fixture(scope="session") def test_engine() -> Engine: + subprocess.run(["python3", "-m", "backend.script.create_database"]) session = db_session() return session @pytest.fixture(scope="function") def session(test_engine: Engine): - # entities.EntityBase.metadata.drop_all(test_engine) - # entities.EntityBase.metadata.create_all(test_engine) session = Session(test_engine) try: yield session diff --git a/backend/test/services/sample_test.py b/backend/test/services/sample_test.py index 7d4cfbd..ad2167e 100644 --- a/backend/test/services/sample_test.py +++ b/backend/test/services/sample_test.py @@ -7,3 +7,7 @@ from sqlalchemy import Engine def test_sample(session: Engine): print(session) assert session != None + + +def test_tables(session: Engine): + print() From c6482e7c84cf7ef2c55e416639c171199c6bf62b Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Sat, 2 Mar 2024 15:23:13 -0500 Subject: [PATCH 3/5] change .env in READ.me --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd0d089..c6bdb43 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Also add following variables inside of a .env file inside of the backend directo ``` \\ .env file contents -POSTGRES_DB=compass +POSTGRES_DATABASE=compass POSTGRES_USER=postgres POSTGRES_PASSWORD=admin POSTGRES_HOST=db From eb1eb8dae4750d393c0472cde29b342d0141db9f Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Sat, 9 Mar 2024 21:42:35 -0500 Subject: [PATCH 4/5] add devcontainer setup + python setup --- .devcontainer/Dockerfile | 72 +++++++++++++++++++ .devcontainer/devcontainer.json | 47 ++++++++++++ .../docker-compose.yml | 12 +++- .vscode/extensions.json | 15 ---- .vscode/settings.json | 24 ------- backend/entities/sample_entity.py | 4 +- backend/script/create_database.py | 14 ++++ backend/script/delete_database.py | 14 ++++ backend/script/reset_demo.py | 34 ++------- backend/test/services/conftest.py | 38 ++++++++-- backend/test/services/sample_test.py | 22 ++++-- 11 files changed, 214 insertions(+), 82 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json rename docker-compose.yml => .devcontainer/docker-compose.yml (63%) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json create mode 100644 backend/script/create_database.py create mode 100644 backend/script/delete_database.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..aaab0cb --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,72 @@ +FROM ubuntu:22.04 + +# Setup workspace directory +RUN mkdir /workspace +WORKDIR /workspace + +# Install useful system utilities +ENV TZ=America/New_York +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install --yes \ + apt-transport-https \ + ca-certificates \ + curl \ + debian-keyring \ + debian-archive-keyring \ + git \ + gnupg \ + locales \ + postgresql-client \ + software-properties-common \ + sudo \ + tzdata \ + wget \ + zsh \ + && rm -rf /var/lib/apt/lists/* + +# Install Python 3.11 +RUN add-apt-repository ppa:deadsnakes/ppa \ + && apt update \ + && apt install --yes \ + python3.11 \ + python3-pip \ + libpq-dev \ + python3.11-dev \ + && rm -rf /var/lib/apt/lists* \ + && unlink /usr/bin/python3 \ + && ln -s /usr/bin/python3.11 /usr/bin/python3 + +# Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Add non-root user and add to sudoers +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME -s /usr/bin/zsh \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME + +# Set code to default git commit editor +RUN git config --system core.editor "code --wait" +# Set Safe Directory +RUN git config --system safe.directory '/workspace' + +# Configure zsh +USER $USERNAME +ENV HOME /home/$USERNAME + +# Add zsh theme with niceties +RUN curl https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash - \ + && sed -i 's/robbyrussell/kennethreitz/g' ~/.zshrc \ + && echo 'source <(ng completion script)' >>~/.zshrc \ + && echo 'export PATH=$PATH:$HOME/.local/bin' >>~/.zshrc + +# Set Locale for Functional Autocompletion in zsh +RUN sudo locale-gen en_US.UTF-8 + +# Install Database Dependencies +COPY backend/requirements.txt /workspace/backend/requirements.txt +WORKDIR /workspace/backend +RUN python3 -m pip install -r requirements.txt \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..793f5d8 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "dockerComposeFile": "docker-compose.yml", + "workspaceFolder": "/workspace", + "service": "httpd", + "remoteUser": "vscode", + "forwardPorts": [ + 5432, + 5050 + ], + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "ecmel.vscode-html-css", + "ms-vscode.vscode-typescript-next", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "vscode-icons-team.vscode-icons", + "tamasfe.even-better-toml", + "ckolkman.vscode-postgres", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.black-formatter", + "gruntfuggly.todo-tree", + "ms-azuretools.vscode-docker" + ], + "settings": { + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.analysis.extraPaths": [ + "/backend/" + ], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingParameterType": "error", + "reportGeneralTypeIssues": "error", + "reportDeprecated": "error", + "reportImportCycles": "error" + } + } + } + } +} \ No newline at end of file diff --git a/docker-compose.yml b/.devcontainer/docker-compose.yml similarity index 63% rename from docker-compose.yml rename to .devcontainer/docker-compose.yml index f2cabf8..1503d5b 100644 --- a/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,12 +1,22 @@ version: "3" services: + httpd: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspace + command: /bin/sh -c "while sleep 1000; do :; done" + environment: + - windir # Defined on Windows but not on other platforms + db: image: "postgres:latest" ports: - "5432:5432" env_file: - - ./backend/.env + - ../backend/.env volumes: - compass-center-postgres:/var/lib/postgresql/data # - ./backend/init.sql:/docker-entrypoint-initdb.d/init.sql:ro diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 11dae3a..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint", - "ecmel.vscode-html-css", - "ms-vscode.vscode-typescript-next", - "esbenp.prettier-vscode", - "bradlc.vscode-tailwindcss", - "vscode-icons-team.vscode-icons", - "tamasfe.even-better-toml", - "ckolkman.vscode-postgres", - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.black-formatter" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3cf96ac..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "editor.formatOnSave": true, - "editor.formatOnSaveMode": "file", - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[python]": { - "editor.defaultFormatter": "ms-python.autopep8" - }, - "python.analysis.extraPaths": ["backend/"], - "python.testing.pytestEnabled": true, - "python.testing.unittestEnabled": false, - "python.analysis.diagnosticSeverityOverrides": { - "reportMissingParameterType": "error", - "reportGeneralTypeIssues": "error", - "reportDeprecated": "error", - "reportImportCycles": "error" - }, - "python.analysis.autoImportCompletions": false, - "python.analysis.typeCheckingMode": "off" -} diff --git a/backend/entities/sample_entity.py b/backend/entities/sample_entity.py index 28948ff..4759694 100644 --- a/backend/entities/sample_entity.py +++ b/backend/entities/sample_entity.py @@ -4,9 +4,9 @@ from .entity_base import EntityBase class SampleEntity(EntityBase): - __tablename__ = 'persons' + __tablename__ = "persons" - id: Mapped[int] = mapped_column(Integer, primary_key=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String, nullable=False) age: Mapped[int] = mapped_column(Integer) email: Mapped[str] = mapped_column(String, unique=True, nullable=False) diff --git a/backend/script/create_database.py b/backend/script/create_database.py new file mode 100644 index 0000000..3d2f17a --- /dev/null +++ b/backend/script/create_database.py @@ -0,0 +1,14 @@ +from sqlalchemy import text, create_engine +from ..database import engine, _engine_str +from ..env import getenv + +engine = create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + +with engine.connect() as connection: + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"CREATE DATABASE {database}") + connection.execute(stmt) \ No newline at end of file diff --git a/backend/script/delete_database.py b/backend/script/delete_database.py new file mode 100644 index 0000000..0679da1 --- /dev/null +++ b/backend/script/delete_database.py @@ -0,0 +1,14 @@ +from sqlalchemy import text, create_engine +from ..database import engine, _engine_str +from ..env import getenv + +engine = create_engine(_engine_str(), echo=True) +"""Application-level SQLAlchemy database engine.""" + +with engine.connect() as connection: + connection.execute( + text("COMMIT") + ) + database = getenv("POSTGRES_DATABASE") + stmt = text(f"DROP DATABASE IF EXISTS {database}") + connection.execute(stmt) \ No newline at end of file diff --git a/backend/script/reset_demo.py b/backend/script/reset_demo.py index 65de426..da06da8 100644 --- a/backend/script/reset_demo.py +++ b/backend/script/reset_demo.py @@ -1,38 +1,18 @@ -from sqlalchemy import text, create_engine -from ..database import engine +from sqlalchemy import create_engine +import subprocess + +from ..database import engine, _engine_str from ..env import getenv from .. import entities database = getenv("POSTGRES_DATABASE") - -def _engine_str() -> str: - """Helper function for reading settings from environment variables to produce connection string.""" - dialect = "postgresql+psycopg2" - user = getenv("POSTGRES_USER") - password = getenv("POSTGRES_PASSWORD") - host = getenv("POSTGRES_HOST") - port = getenv("POSTGRES_PORT") - return f"{dialect}://{user}:{password}@{host}:{port}" - - engine = create_engine(_engine_str(), echo=True) """Application-level SQLAlchemy database engine.""" - -with engine.connect() as connection: - connection.execute( - text("COMMIT") - ) - database = getenv("POSTGRES_DATABASE") - stmt = text(f"DROP DATABASE IF EXISTS {database}") - connection.execute(stmt) - connection.execute( - text("COMMIT") - ) - database = getenv("POSTGRES_DATABASE") - stmt = text(f"CREATE DATABASE {database}") - connection.execute(stmt) +# Run Delete and Create Database Scripts +subprocess.run(["python3", "-m", "backend.script.delete_database"]) +subprocess.run(["python3", "-m", "backend.script.create_database"]) entities.EntityBase.metadata.drop_all(engine) entities.EntityBase.metadata.create_all(engine) diff --git a/backend/test/services/conftest.py b/backend/test/services/conftest.py index 42b2fdd..5b2a984 100644 --- a/backend/test/services/conftest.py +++ b/backend/test/services/conftest.py @@ -1,22 +1,48 @@ """Shared pytest fixtures for database dependent tests.""" import pytest -from sqlalchemy import Engine +from sqlalchemy import Engine, create_engine, text from sqlalchemy.orm import Session -import subprocess +from sqlalchemy.exc import OperationalError -from ...database import db_session +from ...database import _engine_str +from ...env import getenv +from ... import entities + +POSTGRES_DATABASE = f'{getenv("POSTGRES_DATABASE")}_test' +POSTGRES_USER = getenv("POSTGRES_USER") + +def reset_database(): + engine = create_engine(_engine_str(database="")) + with engine.connect() as connection: + try: + conn = connection.execution_options(autocommit=False) + conn.execute(text("ROLLBACK")) # Get out of transactional mode... + conn.execute(text(f"DROP DATABASE IF EXISTS {POSTGRES_DATABASE}")) + except OperationalError: + print( + "Could not drop database because it's being accessed by others (psql open?)" + ) + exit(1) + + conn.execute(text(f"CREATE DATABASE {POSTGRES_DATABASE}")) + conn.execute( + text( + f"GRANT ALL PRIVILEGES ON DATABASE {POSTGRES_DATABASE} TO {POSTGRES_USER}" + ) + ) @pytest.fixture(scope="session") def test_engine() -> Engine: - subprocess.run(["python3", "-m", "backend.script.create_database"]) - session = db_session() - return session + reset_database() + return create_engine(_engine_str(POSTGRES_DATABASE)) @pytest.fixture(scope="function") def session(test_engine: Engine): + entities.EntityBase.metadata.drop_all(test_engine) + entities.EntityBase.metadata.create_all(test_engine) session = Session(test_engine) try: yield session diff --git a/backend/test/services/sample_test.py b/backend/test/services/sample_test.py index ad2167e..f21423e 100644 --- a/backend/test/services/sample_test.py +++ b/backend/test/services/sample_test.py @@ -1,13 +1,21 @@ """Sample Test File""" -import pytest -from sqlalchemy import Engine +from sqlalchemy import Engine, select + +from ... import entities +from ...entities.sample_entity import SampleEntity -def test_sample(session: Engine): - print(session) - assert session != None +def test_entity_count(): + """Checks the number of entities to be inserted""" + print(entities.EntityBase.metadata.tables.keys()) + assert len(entities.EntityBase.metadata.tables.keys()) == 1 -def test_tables(session: Engine): - print() +def test_add_sample_data(session: Engine): + """Inserts a sample data point and verifies it is in the database""" + entity = SampleEntity(name="Praj", age=19, email="pmoha@unc.edu") + session.add(entity) + session.commit() + data = session.get(SampleEntity, 1) + assert data.name == "Praj" From e2d871df19cd161f990013aba949951c6664d968 Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Sat, 9 Mar 2024 22:23:17 -0500 Subject: [PATCH 5/5] update readme + restructure tests --- README.md | 40 ++++++++----------- backend/script/create_database.py | 2 +- backend/script/delete_database.py | 2 +- .../test/{services => entities}/__init__.py | 0 .../test/{services => entities}/conftest.py | 0 .../{services => entities}/sample_test.py | 0 6 files changed, 19 insertions(+), 25 deletions(-) rename backend/test/{services => entities}/__init__.py (100%) rename backend/test/{services => entities}/conftest.py (100%) rename backend/test/{services => entities}/sample_test.py (100%) diff --git a/README.md b/README.md index c6bdb43..21b84a4 100644 --- a/README.md +++ b/README.md @@ -47,33 +47,26 @@ POSTGRES_PORT=5432 HOST=localhost ``` -Install necessary python packages - -``` -\\ Change directory from compass into backend directory -cd backend - -\\ Install python dependencies -pip3 install -r requirements.txt -``` - ## Backend Starter -Follow these steps to start up Postgres database: +- Please open the VS Code Command Palette +- Run the command **Dev Containers: Rebuild and Reopen in Container** +- This should open the dev container with the same file directory mounted so any changes in the dev container will be seen in the local repo -Make sure you have Docker installed! +### In Dev Container +Run this to reset the database and populate it with the approprate tables that reflect the entities folder +``` +python3 -m backend.script.reset_demo ``` -\\ Spins up local postgres database and pgadmin -docker-compose up -d +### Possible Dev Container Errors -\\ Stop and teardown containers -docker-compose down - -\\ Stop and teardown containers + volumes (full reset) -docker-compose down -v -``` +- Sometimes the ports allocated to our services will be allocated (5432 for Postgres and 5050 for PgAdmin4) +- Run **docker stop** to stop all containers +- If that does not work using **sudo lsof -i :[PORT_NUMBER]** to find the process running on the needed ports and idenitfy the PID +- Run **sudo kill [PID]** +- If you are on Windows please consult ChatGPT or set up WSL (will be very useful in the future) ### Accesing pgAdmin 4 @@ -89,11 +82,11 @@ docker-compose down -v - Click **Save** at the bottom to add connection - Click **Server** dropdown on the left and click through items inside the **Compass** server -## Testing Backend Code +### Testing Backend Code - Write tests for any service you create and any function in those services - Make sure to add docstrings detailing what the file is doing and what each test is doing -- Name all test functions with test\_[testContent]() +- Name all test functions with test\_[testContent] (Must be prefixed with test to be recognized by pytest) - Utitlize dependency injection for commonly used services ``` @@ -101,7 +94,8 @@ docker-compose down -v pytest \\ Run specific tests by passing in file as a parameter -pytest [fileName] +\\ Passing the -s allows us to see any print statements or debugging statements in the console +pytest -s --rootdir=/workspace [testFilePath]::[testFunctionSignature] ``` ## 💡 Dev Notes diff --git a/backend/script/create_database.py b/backend/script/create_database.py index 3d2f17a..197176f 100644 --- a/backend/script/create_database.py +++ b/backend/script/create_database.py @@ -2,7 +2,7 @@ from sqlalchemy import text, create_engine from ..database import engine, _engine_str from ..env import getenv -engine = create_engine(_engine_str(), echo=True) +engine = create_engine(_engine_str(database=""), echo=True) """Application-level SQLAlchemy database engine.""" with engine.connect() as connection: diff --git a/backend/script/delete_database.py b/backend/script/delete_database.py index 0679da1..44b3f6a 100644 --- a/backend/script/delete_database.py +++ b/backend/script/delete_database.py @@ -2,7 +2,7 @@ from sqlalchemy import text, create_engine from ..database import engine, _engine_str from ..env import getenv -engine = create_engine(_engine_str(), echo=True) +engine = create_engine(_engine_str(database=""), echo=True) """Application-level SQLAlchemy database engine.""" with engine.connect() as connection: diff --git a/backend/test/services/__init__.py b/backend/test/entities/__init__.py similarity index 100% rename from backend/test/services/__init__.py rename to backend/test/entities/__init__.py diff --git a/backend/test/services/conftest.py b/backend/test/entities/conftest.py similarity index 100% rename from backend/test/services/conftest.py rename to backend/test/entities/conftest.py diff --git a/backend/test/services/sample_test.py b/backend/test/entities/sample_test.py similarity index 100% rename from backend/test/services/sample_test.py rename to backend/test/entities/sample_test.py