diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9d866e3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..e1e14af --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,24 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install pylint + - name: Analysing the code with pylint + run: | + pylint --disable=C0114,C0115,C0116 --exit-zero $(git ls-files '*.py') diff --git a/.gitignore b/.gitignore index 08fdd16..e36b675 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,144 @@ +# Database files database.db -__pycache__ + +# 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/ +build/ +temp/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.env.* +.venv +venv/ +ENV/ +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/ + +# IDE settings +.vscode/ +.idea/ \ No newline at end of file diff --git a/.idea/Pilgrim.iml b/.idea/Pilgrim.iml deleted file mode 100644 index 3defecc..0000000 --- a/.idea/Pilgrim.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index daedced..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 3586794..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1748985568579 - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index d9baacb..7d8d17a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,50 @@ # Python_Pilgrim -Python Based Travel Diary \ No newline at end of file +## Overview + +**Python_Pilgrim** is a Python-based travel diary application designed to help users document and manage their travel experiences. The project provides tools for recording trips, organizing travel notes, and storing memories in a structured and accessible format. + +## Features + +- Create and manage travel diaries +- Add, edit, and delete travel entries +- Organize trips by date, location, or theme +- Store photos, notes, and other media +- Export and share travel logs + +## Requirements +- Python 3.8 or higher +- Linux operating system (tested on Ubuntu 20.04+) +- Visual Studio Code (VSCode) for development (optional but strongly recommended) +- pip (Python package installer) +- Optional: virtualenv for isolated environments + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/gmbrax/Pilgrim.git + ``` +2. Navigate to the project directory: + ```bash + cd Pilgrim + ``` +3. Create a virtual environment and, then, activate it: + ```bash + python -m venv .venv + source .venv/bin/activate + ``` +4. Install the required dependencies: + ```bash + pip install -r requirements.txt + ``` + +## Usage + +To run the main application, execute: + +```bash +python .py +``` + +This will start the Python_Pilgrim application. Follow the on-screen instructions to create and manage your travel diaries. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6f29a00..805875b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,6 @@ src-layout = true Homepage = "https://git.gustavomiranda.xyz/GHMiranda/Pilgrim" Issues = "https://git.gustavomiranda.xyz/GHMiranda/Pilgrim" [tool.hatch.build.targets.wheel] -packages = ["src/Pilgrim"] +packages = ["src/pilgrim"] [project.scripts] -pilgrim = "Pilgrim:main" +pilgrim = "pilgrim:main" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..182fec2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +greenlet==3.2.3 +SQLAlchemy==2.0.41 +typing_extensions==4.14.0 \ No newline at end of file diff --git a/src/Pilgrim/Application.py b/src/Pilgrim/Application.py deleted file mode 100644 index 429798b..0000000 --- a/src/Pilgrim/Application.py +++ /dev/null @@ -1,9 +0,0 @@ -from Pilgrim.Database import Database - - -class Application: - def __init__(self): - self.database = Database() - - def run(self): - self.database.create() diff --git a/src/Pilgrim/TravelDiary.py b/src/Pilgrim/TravelDiary.py deleted file mode 100644 index ea03940..0000000 --- a/src/Pilgrim/TravelDiary.py +++ /dev/null @@ -1,9 +0,0 @@ -from sqlalchemy import Column, String, Integer - -from Pilgrim import Application, Base - - -class TravelDiary(Base): - __tablename__ = "TravelDiary" - id = Column(Integer, primary_key=True) - name = Column(String) diff --git a/src/Pilgrim/__init__.py b/src/Pilgrim/__init__.py deleted file mode 100644 index 201964a..0000000 --- a/src/Pilgrim/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from Pilgrim.Application import Application -from Pilgrim.command import main -from Pilgrim.Database import Database, Base -from Pilgrim.TravelDiary import TravelDiary - -__all__ = ["Application", "Database", "TravelDiary", "main", "Base"] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/application.py b/src/application.py new file mode 100644 index 0000000..2dcb79a --- /dev/null +++ b/src/application.py @@ -0,0 +1,16 @@ +from src.database import Database +from src.service.servicemanager import ServiceManager + + +class Application: + def __init__(self): + self.database = Database() + + def run(self): + self.database.create() + + def get_service_manager(self): + session = self.database.session() + session_manager = ServiceManager() + session_manager.set_session(session) + return session_manager diff --git a/src/Pilgrim/command.py b/src/command.py similarity index 56% rename from src/Pilgrim/command.py rename to src/command.py index d132d04..3d23d5a 100644 --- a/src/Pilgrim/command.py +++ b/src/command.py @@ -1,4 +1,4 @@ -from Pilgrim import Application +from src.application import Application def main(): diff --git a/src/Pilgrim/Database.py b/src/database.py similarity index 83% rename from src/Pilgrim/Database.py rename to src/database.py index d28fdee..794d698 100644 --- a/src/Pilgrim/Database.py +++ b/src/database.py @@ -4,7 +4,6 @@ from sqlalchemy.orm import sessionmaker Base = declarative_base() - class Database: def __init__(self): self.engine = create_engine( @@ -12,10 +11,10 @@ class Database: echo=False, connect_args={"check_same_thread": False}, ) - self.Session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False) + self.session = sessionmaker(bind=self.engine, autoflush=False, autocommit=False) def create(self): Base.metadata.create_all(self.engine) def get_db(self): - return self.Session() + return self.session() diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/entry.py b/src/models/entry.py new file mode 100644 index 0000000..8775faf --- /dev/null +++ b/src/models/entry.py @@ -0,0 +1,26 @@ +from typing import Any + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship + +from src.models.photo_in_entry import photo_entry_association +from src.database import Base + + +class Entry(Base): + __tablename__ = "entries" + id = Column(Integer, primary_key=True) + title = Column(String) + text = Column(String) + date = Column(String) + photos = relationship( + "Photo", + secondary=photo_entry_association, + back_populates="entries") + fk_travel_diary_id = Column(Integer, ForeignKey("travel_diaries.id"),nullable=False) + def __init__(self, title: str, text: str, date: str, travel_diary_id: int, **kw: Any): + super().__init__(**kw) + self.title = title + self.text = text + self.date = date + self.fk_travel_diary_id = travel_diary_id diff --git a/src/models/photo.py b/src/models/photo.py new file mode 100644 index 0000000..d4eb6a9 --- /dev/null +++ b/src/models/photo.py @@ -0,0 +1,31 @@ +from typing import Any + +from sqlalchemy.orm import relationship +from sqlalchemy import Column, Integer, String, ForeignKey + +from src.models.photo_in_entry import photo_entry_association +from src.database import Base + + +class Photo(Base): + __tablename__ = "photos" + id = Column(Integer, primary_key=True) + filepath = Column(String) + name = Column(String) + addition_date = Column(String) + caption = Column(String) + entries = relationship( + "Entry", + secondary=photo_entry_association, + back_populates="photos" + ) + + fk_travel_diary_id = Column(Integer, ForeignKey("travel_diaries.id"),nullable=False) + + def __init__(self, filepath, name, addition_date=None, caption=None, entries=None, **kw: Any): + super().__init__(**kw) + self.filepath = filepath + self.name = name + self.addition_date = addition_date + self.caption = caption + self.entries = entries diff --git a/src/models/photo_in_entry.py b/src/models/photo_in_entry.py new file mode 100644 index 0000000..d07d4fa --- /dev/null +++ b/src/models/photo_in_entry.py @@ -0,0 +1,8 @@ +from sqlalchemy import Table, Column, Integer, ForeignKey + +from src.database import Base + +photo_entry_association = Table('photo_entry_association', Base.metadata, +Column('id', Integer, primary_key=True, autoincrement=True), + Column('fk_photo_id', Integer, ForeignKey('photos.id'),nullable=False), + Column('fk_entry_id', Integer, ForeignKey('entries.id'),nullable=False)) diff --git a/src/models/travel_diary.py b/src/models/travel_diary.py new file mode 100644 index 0000000..15c8579 --- /dev/null +++ b/src/models/travel_diary.py @@ -0,0 +1,15 @@ +from typing import Any + +from src.database import Base + +from sqlalchemy import Column, String, Integer + + +class TravelDiary(Base): + __tablename__ = "travel_diaries" + id = Column(Integer, primary_key=True) + name = Column(String) + + def __init__(self, name: str, **kw: Any): + super().__init__(**kw) + self.name = name diff --git a/src/service/__init__.py b/src/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/service/entry_service.py b/src/service/entry_service.py new file mode 100644 index 0000000..2afedd0 --- /dev/null +++ b/src/service/entry_service.py @@ -0,0 +1,48 @@ +from typing import List + +from src.models.entry import Entry +from src.models.travel_diary import TravelDiary + + +class EntryService: + def __init__(self,session): + self.session = session + + def create(self, travel_diary_id:int, title: str, text: str, date: str, ): + travel_diary = self.session.query(TravelDiary).filter(TravelDiary.id == travel_diary_id).first() + if not travel_diary: + return None + new_entry = Entry(title,text,date,travel_diary_id) + self.session.add(new_entry) + self.session.commit() + self.session.refresh(new_entry) + return new_entry + + def read_by_id(self,entry_id:int)->Entry: + entry = self.session.query(Entry).filter(Entry.id == entry_id).first() + return entry + + def read_all(self)-> List[Entry]: + entries = self.session.query(Entry).all() + return entries + + def update(self,entry_src:Entry,entry_dst:Entry) -> Entry | None: + original:Entry = self.read_by_id(entry_src.id) + if original: + original.title = entry_dst.title + original.text = entry_dst.text + original.date = entry_dst.date + original.fk_travel_diary_id = entry_dst.fk_travel_diary_id + original.photos = entry_dst.photos + self.session.commit() + self.session.refresh(original) + return original + return None + + def delete(self,entry_src:Entry)-> Entry | None: + excluded = self.read_by_id(entry_src.id) + if excluded is not None: + self.session.delete(excluded) + self.session.commit() + return excluded + return None diff --git a/src/service/photo_service.py b/src/service/photo_service.py new file mode 100644 index 0000000..2232ad1 --- /dev/null +++ b/src/service/photo_service.py @@ -0,0 +1,48 @@ +from pathlib import Path +from typing import List + +from src.models.photo import Photo +from src.models.travel_diary import TravelDiary + + +class PhotoService: + def __init__(self, session): + self.session = session + + def create(self, filepath:Path, name:str, travel_diary_id, addition_date=None, caption=None, ) -> Photo | None: + travel_diary = self.session.query(TravelDiary).filter(TravelDiary.id == travel_diary_id).first() + if not travel_diary: + return None + new_photo = Photo(filepath, name, addition_date=addition_date, caption=caption) + self.session.add(new_photo) + self.session.commit() + self.session.refresh(new_photo) + + return new_photo + def read_by_id(self, photo_id:int) -> Photo: + return self.session.query(Photo).get(photo_id) + + def read_all(self) -> List[Photo]: + return self.session.query(Photo).all() + + def update(self,photo_src:Photo,photo_dst:Photo) -> Photo | None: + original:Photo = self.read_by_id(photo_src.id) + if original: + original.filepath = photo_dst.filepath + original.name = photo_dst.name + original.addition_date = photo_dst.addition_date + original.caption = photo_dst.caption + original.entries.extend(photo_dst.entries) + self.session.commit() + self.session.refresh(original) + return original + return None + + def delete(self, photo_src:Photo) -> Photo | None: + excluded = self.read_by_id(photo_src.id) + if excluded: + self.session.delete(excluded) + self.session.commit() + self.session.refresh(excluded) + return excluded + return None diff --git a/src/service/servicemanager.py b/src/service/servicemanager.py new file mode 100644 index 0000000..3e9c3a6 --- /dev/null +++ b/src/service/servicemanager.py @@ -0,0 +1,19 @@ +from src.service.entry_service import EntryService +from src.service.travel_diary_service import TravelDiaryService + + +class ServiceManager: + def __init__(self): + self.session = None + def set_session(self, session): + self.session = session + def get_session(self): + return self.session + def get_entry_service(self): + if self.session is not None: + return EntryService(self.session) + return None + def get_travel_diary_service(self): + if self.session is not None: + return TravelDiaryService(self.session) + return None diff --git a/src/service/travel_diary_service.py b/src/service/travel_diary_service.py new file mode 100644 index 0000000..846f27d --- /dev/null +++ b/src/service/travel_diary_service.py @@ -0,0 +1,35 @@ +from src.models.travel_diary import TravelDiary + + +class TravelDiaryService: + def __init__(self,session): + self.session = session + def create(self, name:str): + new_travel_diary = TravelDiary(name) + self.session.add(new_travel_diary) + self.session.commit() + self.session.refresh(new_travel_diary) + + return new_travel_diary + + def read_by_id(self, travel_id:int): + return self.session.query(TravelDiary).get(travel_id) + + def read_all(self): + return self.session.query(TravelDiary).all() + + def update(self, travel_diary_src:TravelDiary,travel_diary_dst:TravelDiary): + original = self.read_by_id(travel_diary_src.id) + if original is not None: + original.name = travel_diary_dst.name + self.session.commit() + self.session.refresh(original) + return original + + def delete(self, travel_diary_src:TravelDiary): + excluded = self.read_by_id(travel_diary_src.id) + if excluded is not None: + self.session.delete(travel_diary_src) + self.session.commit() + return excluded + return None