Bump version to 0.2.0 and add alembic as a dependency.

The commit also includes changes to add Alembic configuration file, add migration scripts for initial migration, and modify the database class to include functions for running migrations and creating new migrations.
This commit is contained in:
Kumi 2023-08-29 10:30:45 +02:00
parent eb87e38507
commit 23407f031e
Signed by: kumi
GPG key ID: ECBCC9082395383F
6 changed files with 183 additions and 3 deletions

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "trackbert"
version = "0.1.3"
version = "0.2.0"
authors = [
{ name="Kumi Mitterer", email="trackbert@kumi.email" },
]
@ -23,6 +23,7 @@ dependencies = [
"glsapi",
"fedextrack",
"sqlalchemy",
"alembic",
"python-dateutil",
]

View file

@ -0,0 +1,2 @@
[alembic]
version_path_separator = os

View file

@ -3,7 +3,13 @@ from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
from alembic.config import Config
from alembic import command
import json
import time
from pathlib import Path
Base = declarative_base()
@ -35,6 +41,8 @@ class Database:
Session = sessionmaker(bind=self.engine)
self.session = Session()
self.run_migrations()
def create_shipment(self, tracking_number, carrier, description=""):
new_shipment = Shipment(
tracking_number=tracking_number, carrier=carrier, description=description
@ -102,5 +110,16 @@ class Database:
)
return event
def initialize_db(self):
Base.metadata.create_all(self.engine)
def make_migration(self, message):
alembic_cfg = Config(Path(__file__).parent.parent / "alembic.ini")
alembic_cfg.set_main_option("sqlalchemy.url", self.engine.url.__to_string__(hide_password=False))
migrations_dir = Path(__file__).parent.parent / 'migrations'
alembic_cfg.set_main_option("script_location", str(migrations_dir))
command.revision(alembic_cfg, message=message, autogenerate=True)
def run_migrations(self):
alembic_cfg = Config(Path(__file__).parent.parent / "alembic.ini")
alembic_cfg.set_main_option("sqlalchemy.url", self.engine.url.__to_string__(hide_password=False))
migrations_dir = Path(__file__).parent.parent / 'migrations'
alembic_cfg.set_main_option("script_location", str(migrations_dir))
command.upgrade(alembic_cfg, "head")

View file

@ -0,0 +1,72 @@
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from trackbert.classes.database import Base
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View file

@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,60 @@
"""Initial migration
Revision ID: 770fdbef1f4e
Revises:
Create Date: 2023-08-29 10:01:10.100731
"""
from typing import Sequence, Union
from alembic import op
from sqlalchemy.exc import ProgrammingError, OperationalError
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "770fdbef1f4e"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
if not table_exists("shipments"):
op.create_table(
"shipments",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("tracking_number", sa.String(), nullable=True),
sa.Column("carrier", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
if not table_exists("events"):
op.create_table(
"events",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("event_time", sa.String(), nullable=True),
sa.Column("event_description", sa.String(), nullable=True),
sa.Column("raw_event", sa.String(), nullable=True),
sa.Column("shipment_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["shipment_id"],
["shipments.id"],
),
sa.PrimaryKeyConstraint("id"),
)
def downgrade() -> None:
op.drop_table("events")
op.drop_table("shipments")
def table_exists(table_name):
try:
op.execute(f'SELECT 1 FROM "{table_name}" LIMIT 1;')
return True
except (OperationalError, ProgrammingError):
return False