feat: other databases support

This commit is contained in:
Antoine Bertin 2021-04-27 10:19:29 +02:00
commit 7559c66127
No known key found for this signature in database
GPG key ID: 09851B52754E2327
20 changed files with 39 additions and 67 deletions

View file

@ -26,8 +26,8 @@ services:
ports: ports:
- 9921:9000 - 9921:9000
environment: environment:
db_type: sqlite
TZ: America/Anchorage # Specify Correct Timezone for Date/Time to line up correctly. TZ: America/Anchorage # Specify Correct Timezone for Date/Time to line up correctly.
DB_URL: sqlite:///mealie.db
volumes: volumes:
- ./dev/data:/app/dev/data - ./dev/data:/app/dev/data
- ./mealie:/app/mealie - ./mealie:/app/mealie

View file

@ -9,4 +9,4 @@ services:
ports: ports:
- 9090:80 - 9090:80
environment: environment:
db_type: sqlite DB_URL: sqlite:///mealie.db

View file

@ -25,7 +25,7 @@ Deployment with the Docker CLI can be done with `docker run` and specify the dat
```shell ```shell
docker run \ docker run \
-e DB_TYPE='sqlite' \ -e DB_URL='sqlite:///mealie.db' \
-p 9925:80 \ -p 9925:80 \
-v `pwd`:'/app/data/' \ -v `pwd`:'/app/data/' \
hkotel/mealie:latest hkotel/mealie:latest
@ -50,7 +50,7 @@ services:
ports: ports:
- 9925:80 - 9925:80
environment: environment:
DB_TYPE: sqlite DB_URL: sqlite:///mealie.db
TZ: America/Anchorage TZ: America/Anchorage
volumes: volumes:
- ./mealie/data/:/app/data - ./mealie/data/:/app/data
@ -60,8 +60,8 @@ services:
## Env Variables ## Env Variables
| Variables | Default | Description | | Variables | Default | Description |
| ---------------- | ------------------ | ----------------------------------------------------------------------------------- | | ---------------- | ------------------- | ----------------------------------------------------------------------------------- |
| DB_TYPE | sqlite | The database type to be used. Current Options 'sqlite' | | DB_URL | sqlite:///mealie.db | The database url to be used. See [SQLAlchemy documentation][1] for more options |
| DEFAULT_GROUP | Home | The default group for users | | DEFAULT_GROUP | Home | The default group for users |
| DEFAULT_EMAIL | changeme@email.com | The default username for the superuser | | DEFAULT_EMAIL | changeme@email.com | The default username for the superuser |
| DEFAULT_PASSWORD | MyPassword | The default password for the superuser | | DEFAULT_PASSWORD | MyPassword | The default password for the superuser |
@ -71,7 +71,7 @@ services:
| TZ | UTC | Must be set to get correct date/time on the server | | TZ | UTC | Must be set to get correct date/time on the server |
[1]: https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls
## Advanced ## Advanced
!!! warning "Not Required" !!! warning "Not Required"

View file

@ -57,7 +57,6 @@ class AppDirectories:
self.CHOWDOWN_DIR: Path = self.MIGRATION_DIR.joinpath("chowdown") self.CHOWDOWN_DIR: Path = self.MIGRATION_DIR.joinpath("chowdown")
self.TEMPLATE_DIR: Path = data_dir.joinpath("templates") self.TEMPLATE_DIR: Path = data_dir.joinpath("templates")
self.USER_DIR: Path = data_dir.joinpath("users") self.USER_DIR: Path = data_dir.joinpath("users")
self.SQLITE_DIR: Path = data_dir.joinpath("db")
self.RECIPE_DATA_DIR: Path = data_dir.joinpath("recipes") self.RECIPE_DATA_DIR: Path = data_dir.joinpath("recipes")
self.TEMP_DIR: Path = data_dir.joinpath(".temp") self.TEMP_DIR: Path = data_dir.joinpath(".temp")
@ -70,7 +69,6 @@ class AppDirectories:
self.DEBUG_DIR, self.DEBUG_DIR,
self.MIGRATION_DIR, self.MIGRATION_DIR,
self.TEMPLATE_DIR, self.TEMPLATE_DIR,
self.SQLITE_DIR,
self.NEXTCLOUD_DIR, self.NEXTCLOUD_DIR,
self.CHOWDOWN_DIR, self.CHOWDOWN_DIR,
self.RECIPE_DATA_DIR, self.RECIPE_DATA_DIR,
@ -100,21 +98,7 @@ class AppSettings(BaseSettings):
return "/redoc" if self.API_DOCS else None return "/redoc" if self.API_DOCS else None
SECRET: str = determine_secrets(DATA_DIR, PRODUCTION) SECRET: str = determine_secrets(DATA_DIR, PRODUCTION)
DATABASE_TYPE: str = Field("sqlite", env="DB_TYPE") DB_URL: str = Field("sqlite:///mealie.db", env="DB_URL")
@validator("DATABASE_TYPE", pre=True)
def validate_db_type(cls, v: str) -> Optional[str]:
if v != "sqlite":
raise ValueError("Unable to determine database type. Acceptible options are 'sqlite'")
else:
return v
# Used to Set SQLite File Version
SQLITE_FILE: Optional[Union[str, Path]]
@validator("SQLITE_FILE", pre=True)
def identify_sqlite_file(cls, v: str) -> Optional[str]:
return app_dirs.SQLITE_DIR.joinpath(f"mealie_{DB_VERSION}.sqlite")
DEFAULT_GROUP: str = "Home" DEFAULT_GROUP: str = "Home"
DEFAULT_EMAIL: str = "changeme@email.com" DEFAULT_EMAIL: str = "changeme@email.com"

View file

@ -5,8 +5,7 @@ from mealie.db.models.db_session import sql_global_init
sql_exists = True sql_exists = True
sql_exists = settings.SQLITE_FILE.is_file() SessionLocal = sql_global_init(settings.DB_URL)
SessionLocal = sql_global_init(settings.SQLITE_FILE)
def create_session() -> Session: def create_session() -> Session:

View file

@ -5,15 +5,10 @@ from mealie.db.models.model_base import SqlAlchemyBase
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
def sql_global_init(db_file: Path, check_thread=False): def sql_global_init(db_url: str):
SQLALCHEMY_DATABASE_URL = "sqlite:///" + str(db_file.absolute())
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = sa.create_engine( engine = sa.create_engine(
SQLALCHEMY_DATABASE_URL, db_url,
echo=False, echo=False,
connect_args={"check_same_thread": check_thread},
) )
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View file

@ -9,7 +9,7 @@ from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
class Meal(SqlAlchemyBase): class Meal(SqlAlchemyBase):
__tablename__ = "meal" __tablename__ = "meal"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("mealplan.uid")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("mealplan.uid"))
slug = sa.Column(sa.String) slug = sa.Column(sa.String)
name = sa.Column(sa.String) name = sa.Column(sa.String)
date = sa.Column(sa.Date) date = sa.Column(sa.Date)
@ -30,7 +30,7 @@ class MealPlanModel(SqlAlchemyBase, BaseMixins):
startDate = sa.Column(sa.Date) startDate = sa.Column(sa.Date)
endDate = sa.Column(sa.Date) endDate = sa.Column(sa.Date)
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan") meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
group_id = sa.Column(sa.String, sa.ForeignKey("groups.id")) group_id = sa.Column(sa.Integer, sa.ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="mealplans") group = orm.relationship("Group", back_populates="mealplans")
def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None: def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None:

View file

@ -5,7 +5,7 @@ from mealie.db.models.model_base import SqlAlchemyBase
class ApiExtras(SqlAlchemyBase): class ApiExtras(SqlAlchemyBase):
__tablename__ = "api_extras" __tablename__ = "api_extras"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
key_name = sa.Column(sa.String, unique=True) key_name = sa.Column(sa.String, unique=True)
value = sa.Column(sa.String) value = sa.Column(sa.String)

View file

@ -6,7 +6,7 @@ class RecipeIngredient(SqlAlchemyBase):
__tablename__ = "recipes_ingredients" __tablename__ = "recipes_ingredients"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
position = sa.Column(sa.Integer) position = sa.Column(sa.Integer)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
ingredient = sa.Column(sa.String) ingredient = sa.Column(sa.String)
def update(self, ingredient): def update(self, ingredient):

View file

@ -5,7 +5,7 @@ from mealie.db.models.model_base import SqlAlchemyBase
class RecipeInstruction(SqlAlchemyBase): class RecipeInstruction(SqlAlchemyBase):
__tablename__ = "recipe_instructions" __tablename__ = "recipe_instructions"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
position = sa.Column(sa.Integer) position = sa.Column(sa.Integer)
type = sa.Column(sa.String, default="") type = sa.Column(sa.String, default="")
text = sa.Column(sa.String) text = sa.Column(sa.String)

View file

@ -5,7 +5,7 @@ from mealie.db.models.model_base import SqlAlchemyBase
class Note(SqlAlchemyBase): class Note(SqlAlchemyBase):
__tablename__ = "notes" __tablename__ = "notes"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
title = sa.Column(sa.String) title = sa.Column(sa.String)
text = sa.Column(sa.String) text = sa.Column(sa.String)

View file

@ -5,7 +5,7 @@ from mealie.db.models.model_base import SqlAlchemyBase
class Nutrition(SqlAlchemyBase): class Nutrition(SqlAlchemyBase):
__tablename__ = "recipe_nutrition" __tablename__ = "recipe_nutrition"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
calories = sa.Column(sa.String) calories = sa.Column(sa.String)
fatContent = sa.Column(sa.String) fatContent = sa.Column(sa.String)
fiberContent = sa.Column(sa.String) fiberContent = sa.Column(sa.String)

View file

@ -11,7 +11,11 @@ recipes2tags = sa.Table(
"recipes2tags", "recipes2tags",
SqlAlchemyBase.metadata, SqlAlchemyBase.metadata,
sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")), sa.Column("recipe_id", sa.Integer, sa.ForeignKey("recipes.id")),
<<<<<<< HEAD
sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")), sa.Column("tag_id", sa.Integer, sa.ForeignKey("tags.id")),
=======
sa.Column("tag_slug", sa.String, sa.ForeignKey("tags.slug")),
>>>>>>> e2e2ad5 (feat: other databases support)
) )

View file

@ -5,7 +5,7 @@ from mealie.db.models.model_base import SqlAlchemyBase
class Tool(SqlAlchemyBase): class Tool(SqlAlchemyBase):
__tablename__ = "tools" __tablename__ = "tools"
id = sa.Column(sa.Integer, primary_key=True) id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.String, sa.ForeignKey("recipes.id")) parent_id = sa.Column(sa.Integer, sa.ForeignKey("recipes.id"))
tool = sa.Column(sa.String) tool = sa.Column(sa.String)
def __init__(self, tool) -> None: def __init__(self, tool) -> None:

View file

@ -16,7 +16,7 @@ class User(SqlAlchemyBase, BaseMixins):
full_name = Column(String, index=True) full_name = Column(String, index=True)
email = Column(String, unique=True, index=True) email = Column(String, unique=True, index=True)
password = Column(String) password = Column(String)
group_id = Column(String, ForeignKey("groups.id")) group_id = Column(Integer, ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="users") group = orm.relationship("Group", back_populates="users")
admin = Column(Boolean, default=False) admin = Column(Boolean, default=False)

View file

@ -20,8 +20,7 @@ async def get_debug_info(current_user=Depends(get_current_user)):
demo_status=settings.IS_DEMO, demo_status=settings.IS_DEMO,
api_port=settings.API_PORT, api_port=settings.API_PORT,
api_docs=settings.API_DOCS, api_docs=settings.API_DOCS,
db_type=settings.DATABASE_TYPE, db_url=settings.DB_URL,
sqlite_file=settings.SQLITE_FILE,
default_group=settings.DEFAULT_GROUP, default_group=settings.DEFAULT_GROUP,
) )

View file

@ -11,6 +11,5 @@ class AppInfo(CamelModel):
class DebugInfo(AppInfo): class DebugInfo(AppInfo):
api_port: int api_port: int
api_docs: bool api_docs: bool
db_type: str db_url: str
sqlite_file: Path
default_group: str default_group: str

View file

@ -14,13 +14,12 @@ API_PORT=9000
# Exposes /docs and /redoc on the server # Exposes /docs and /redoc on the server
API_DOCS=True API_DOCS=True
# Sets the Database type to use. Currently the only supported options is 'sqlite' # Sets the Database URL to use. See https://docs.sqlalchemy.org/en/14/core/engines.html for options.
DB_TYPE=sqlite DB_URL=sqlite:///mealie.db
# Sets the token expiration time in hours. # Sets the token expiration time in hours.
TOKEN_TIME=24 TOKEN_TIME=24
# NOT USED
SFTP_USERNAME=None SFTP_USERNAME=None
SFTP_PASSWORD=None SFTP_PASSWORD=None

View file

@ -16,7 +16,7 @@ SQLITE_FILE = app_dirs.SQLITE_DIR.joinpath("test.db")
SQLITE_FILE.unlink(missing_ok=True) SQLITE_FILE.unlink(missing_ok=True)
TestSessionLocal = sql_global_init(SQLITE_FILE, check_thread=False) TestSessionLocal = sql_global_init(SQLITE_FILE)
init_db(TestSessionLocal()) init_db(TestSessionLocal())

View file

@ -9,14 +9,14 @@ def test_default_settings(monkeypatch):
monkeypatch.delenv("DEFAULT_PASSWORD", raising=False) monkeypatch.delenv("DEFAULT_PASSWORD", raising=False)
monkeypatch.delenv("API_PORT", raising=False) monkeypatch.delenv("API_PORT", raising=False)
monkeypatch.delenv("API_DOCS", raising=False) monkeypatch.delenv("API_DOCS", raising=False)
monkeypatch.delenv("DB_TYPE", raising=False) monkeypatch.delenv("DB_URL", raising=False)
monkeypatch.delenv("IS_DEMO", raising=False) monkeypatch.delenv("IS_DEMO", raising=False)
app_settings = AppSettings() app_settings = AppSettings()
assert app_settings.DEFAULT_GROUP == "Home" assert app_settings.DEFAULT_GROUP == "Home"
assert app_settings.DEFAULT_PASSWORD == "MyPassword" assert app_settings.DEFAULT_PASSWORD == "MyPassword"
assert app_settings.DATABASE_TYPE == "sqlite" assert app_settings.DB_URL == "sqlite:///mealie.db"
assert app_settings.API_PORT == 9000 assert app_settings.API_PORT == 9000
assert app_settings.API_DOCS is True assert app_settings.API_DOCS is True
assert app_settings.IS_DEMO is False assert app_settings.IS_DEMO is False
@ -42,13 +42,6 @@ def test_non_default_settings(monkeypatch):
assert app_settings.DOCS_URL is None assert app_settings.DOCS_URL is None
def test_unknown_database(monkeypatch):
monkeypatch.setenv("DB_TYPE", "nonsense")
with pytest.raises(ValueError, match="Unable to determine database type. Acceptible options are 'sqlite'"):
AppSettings()
def test_secret_generation(tmp_path): def test_secret_generation(tmp_path):
app_dirs = AppDirectories(CWD, DATA_DIR) app_dirs = AppDirectories(CWD, DATA_DIR)
assert determine_secrets(app_dirs.DATA_DIR, False) == "shh-secret-test-key" assert determine_secrets(app_dirs.DATA_DIR, False) == "shh-secret-test-key"