Package: article-rec-db
This branch houses the effort to move our DB code to a SQLAlchemy-based stack and is under active development in parallel with the move to Google Analytics 4. For the current production code, see themainbranch.article-rec-dbDatabase models and migrations for the Local News Lab's article recommendation system.UsageA note before continuing: A lot of the commands you'll see below are wrapped insidePoe tasksdefined inpyproject.toml. Poe is installed as a dev-dependency; runpoe --helpto get a list of tasks and their descriptions. It's entirely possible to run commands without using Poe, but if you decide to use Poe, make sure to read through the tasks to understand what they do.As a packageWe useSQLModel, a layer on top of SQLAlchemy with Pydantic, to define our tables.
This is useful because we can import this package to interact with the tables AND have Pydantic objects in Python
that correspond to a row in the table.To install the package from PyPi, run:pip install article-rec-db. Check existing versionshere.Database managementWe useTerraformto manage cluster entities such as databases, roles and extensions. The code is in theterraformdirectory. Stages (dev and prod) are represented as different databases. To make changes to an existing database,Make changes insideterraform/modules.Runpoe terraform [stage] planto see the changes that will be applied to the corresponding database.At this point, if you're happy, you can runpoe terraform [stage] applyyourself, but we prefer a CI/CD approach. Merging a PR to thedevbranch will trigger a plan to be applied to thedevdatabase, and the same for theprodbranch.We always merge todevfirst, then do another merge fromdevtoprod.Table and column migrationsSo you made some changes to what tables there are, what columns there are, indices, etc. and you'd like to
update the databases. This is what alembic is for! (And notice the difference between Terraform and alembic: Terraform manages database entities that are not specific to a database, like roles and extensions, while alembic manages database entities that are specific to a database, like tables and columns.)To generate a new revision after you've updated the models:Run this from the root of the project:DB_CONNECTION_STRING='postgresql://user:password@host:port/db_name' alembic revision --autogenerate -m "message". (There's a Poe task for this: runpoe rmtdiff -d db_name -m "message")Check the/alembic/versions/directory for the new revision and verify that it does what you want it to. RunTYPE=alembic poe testto test models against a local DB initialized via Alembic, and resolve issues as needed.Run this from the root of the project:DB_CONNECTION_STRING='postgresql://user:password@host:port/db_name' alembic upgrade head. Note that you only need to generate the revision file (step 1)oncebecause we want the same content in each environment's database, but you do need to run theupgrade headcommand oncefor eachdatabase (change the DB_NAME to the desired target). (There's a Poe task for this: runpoe rmtupgrade -d db_name)Similar to database management, we let our CI/CD handle Step 3.Note to LNL devs: Our automated deployment process will run the Terraform changes first, then the Alembic changes. So, for example, using Terraform to create a new database and Alembic to create a new table in that database will work in just one PR, but creating a new table with Alembic and using Terraform to grant a role access to that table won't. Best to divide changes into atomic units, each handled by a single PR.DevelopmentThis project usesPoetryto manage dependencies. It also helps with pinning dependency and python
versions. We also usepre-commitwith hooks forisort,black, andflake8for consistent code style and
readability. Note that this means code that doesn't meet the rules will fail to commit until it is fixed.We usemypyfor static type checking. This can be runmanually,
and the CI runs it on PRs to themainbranch. We also usepytestto run our tests.
This can be runmanuallyand the CI runs it on PRs to themainbranch.SetupInstall Poetry.Runpoetry install --no-rootRunsource $(poetry env list --full-path)/bin/activate && pre-commit install && deactivateto set uppre-commitYou're all set up! Your local environment should include all dependencies, including dev dependencies likeblack.
This is done with Poetry via thepoetry.lockfile.Run Code Format and Lintingpre-commit run --all-filesruns isort, black, and flake8 all in one go, and is also run on every commit.poe formatdoes whatpre-commit run --all-filesdoes and also formats the Terraform code.Run Static Type CheckingTo manually run mypy, simply runmypyfrom the root directory of the project. It will use the default configuration
specified inpyproject.toml.Update DependenciesTo update dependencies in your local environment, make changes to thepyproject.tomlfile then runpoetry updatefrom the root directory of the project.To update Terraform dependencies, make changes toversions.tffiles as necessary.Run TestsTo manually run rests, you need to have a Postgres instance running locally on port 5432. One way to do this
is to run a Docker container, then run the tests while it is active.(If you don't already have the image locally) Rundocker pull ankane/pgvector:v<version used in your remote db>Rundocker run --rm --name postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_HOST_AUTH_METHOD=trust -p 127.0.0.1:5432:5432/tcp postgresRunpytest testsfrom the root directory of the project. Explore thepytestdocs (linked above)Note that if you decide to run the Postgres container with different credentials (a different password, port, etc.) or
via a different method, you will likely need to update the test file to point to the correct Postgres instance.Additionally, if you want to re-run the tests, you want to make sure you start over from a fresh Postgres
instance. If you run Postgres via Docker, you can simplyctrl-Cto stop the image and start a new one.Steps 2 and 3 can be combined into one Poe task:poe test, which also stops the container after the tests are done, even if tests fail. In addition, you can also runpoe lclstartto just start the container, andpoe lclstopto stop it whenever you're done.poe lclconnectwill connect you to the container viapsqlso you can poke around.poe test, by default, is equivalent toTYPE=sqlmodel poe test, which tests the models against a local DB initialized via SQLModel. You can also runTYPE=alembic poe testto test the models against a local DB initialized via Alembic. The first is more convenient and is good for development, the second reflects the production environment more closely and is good for testing Alembic revisions once you're about to submit a PR.