Barcode generation using postgres sequences and pre-defined prefixes.
Baracoda is a JSON-based microservice written in Python and backed in a PostgreSQL database, with the purpose of handling the creation of new barcodes for the LIMS application supported currently in PSD.
These are some of the key features currently supported:
The following tools are required for development:
python (use pyenv
or something similar to install the python version specified in the Pipfile
)
postgresql server and pg_config
library
brew install postgresql
brew link postgresql --force
postgresql@14.1 is used in production. postgresql@14.6 is used in training. postgresql@14.5 is used in UAT.
Create the development database and user using a RDBMS GUI or by running this query in a client:
psql postgres
Create a role for postgres and grant login permissions:
create role postgres LOGIN;
Create the database:
create database baracoda_dev;
grant all privileges on database baracoda_dev to postgres;
pg_config
library will still be needed by the
application), use the docker-compose.yml
file:docker compose up -d
The compose service automatically creates the baracoda_dev
database and postgres
user.
Git hooks are executed using lefthook, install lefthook using homebrew and add the pre-commit and pre-push hooks as follows:
lefthook add pre-commit
lefthook add pre-push
talisman is used as a credentials checker, to ignore
files which it triggers as false positives, follow the instructions in the git commit output by
adding the files to be ignore to a .talismanrc
file and try commit again.
Create and enter the virtual environment:
pipenv shell
Install the required dependencies:
pipenv install --dev
See the Troubleshooting section for any commonly encountered installation issues.
Create the required sequences and tables:
flask init-db
Run the migrations:
flask db upgrade
To run the service:
flask run
The test suite requires a test database, currently named baracoda_test
.
Create the database using a RDBMS GUI or by running this query in a client:
create database baracoda_test;
grant all privileges on database baracoda_test to postgres;
To run the test suite:
python -m pytest -vx
curl --location 'http://localhost:7900/barcodes/SQPD/last'
Response:
{"barcode":"SQPD-3959-C"}
curl --location --request POST 'http://localhost:7900/barcodes/RVI/new' \
--header 'Content-Type: application/json'
Response:
{
"barcode": "RVI-111111"
}
curl --location 'http://localhost:7900/barcodes/RVI/last'
Response:
{
"barcode": "RVI-111111"
}
This project is formatted using black. To run formatting checks, run:
pipenv run black .
This project is linted using flake8. To lint the code, run:
pipenv run flake8
This project uses static type checking provided by the mypy library, to run manually:
pipenv run mypy .
This project uses a Docker image as the unit of deployment. The image is created by GitHub actions.
To trigger the creation of a new image, increment the .release-version
version with the
corresponding change according to semver.
baracoda/orm
folderRun alembic and provide a comment to autogenerate the migration comparing with current database:
flask db revision --autogenerate -m "Added account table"
The following routes are available from this service:
flask routes
Endpoint Methods Rule
-------------------------------------- ------- ----------------------------
barcode_creation.get_last_barcode GET /barcodes/<prefix>/last
barcode_creation.get_new_barcode POST /barcodes/<prefix>/new
barcode_creation.get_new_barcode_group POST /barcodes_group/<prefix>/new
child_barcodes.new_child_barcodes POST /child-barcodes/<prefix>/new
health_check GET /health
static GET /static/<path:filename>
The default configuration of the currently supported prefixes is specified in the
baracoda/config/defaults.py
module. For example:
{
"prefix": "HT",
"sequence_name": "ht",
"formatter_class": GenericBarcodeFormatter,
"enableChildrenCreation": False,
}
These are the allowed keywords that we can specify to configure a prefix:
prefix
: This is the string that represents the prefix we are configuring for
supporting new barcodes.sequence_name
: This is the sequence name in the PostgreSQL database which will
keep record of the last index created for a barcode. Prefixes can share the same
sequence.formatter_class
: Defines the class that will generate the string that represents
a new barcode by using the prefix and the new value obtained from the sequence.
If we want to support a new formatter class we have to provide a class that implements
the interface baracoda.formats.FormatterInterface
.enableChildrenCreation
: Defines if the prefix has enabled the children creation.
If true, the prefix will support creating barcodes based on a parent barcode
If it is False, the prefix will reject any children creation request for that prefix.A formatter class is a way of rendering a new barcode created that can be attached to a prefix.
This can be defined using the configuration (see section Configuration).
Any new Formatter class must extend from the abstract class
baracoda.formats.FormatterInterface
.
The current list of supported formatter classes that can be specified for a prefix is:
Implements the Heron COG UK Id generation that corresponds with format: <Prefix>-<Hexadecimal><Checksum>
.
Examples: ASDF-A34ADA
With description:
Standard barcode generator that follows the format: <Prefix>-<Number>
.
Examples: HT-11811
With description:
Plate barcode generator which supports children creation and checksum. It follows these 2 formats:
<Prefix>-<Number>-<Checksum>
. Examples: SQDP-23-L
<Prefix>-<Number>-<ChildIndex>-<Checksum>
. Examples: LAB-89-2-R
With description:
This section only applies to the barcode formatters that support children barcode creation
which currently only happens with Sequencescape22Formatter
.
Children barcodes from a parent barcode can be created with a POST request to the endpoint with a JSON body:
/child-barcodes/<PREFIX/new
The inputs for this request will be:
/child-barcodes/<PREFIX>/new
All barcodes for the children will have this prefix (example, prefix SS will generate children
like SS-11111-1-L, SS-11111-2-M, etc){'barcode': 'SS-1-1-L', 'count': 2}
.
To be considered valid, the barcode needs to follow the format <PREFIX>-<NUMBER>(-<NUMBER>)?(-<CHECKSUM>)
where the second number part is optional and it represents if the barcode was a child.
For example, valid barcodes would be SS-11111-13-M
(normal parent) and SS-11112-24-N
(parent that was a child) but not SS-1-1-1-L
(several '-') or SS12341-1-L
(no '-' separation).SS-11111-14-R
,
Child would be 14; but for the Parent barcode SS-11111-W
, Child would have no
value defined.A request with parent barcode that does not follow the format defined like:
<PREFIX>-<NUMBER>(-<NUMBER>)?(-<CHECKSUM>)
will create normal barcodes instead of suffixed children barcodes (normal barcode creation).
A request that follows the right format but does not comply with current database will be
rejected as an impostor barcode. For example, if we receive the parent barcode SS-1111-14-N
, but in the
database the parent SS-1111-R
has only created 12 child barcodes yet, so SS-1111-14-N
is
impossible to have been generated by Baracoda.
The following diagram describes the workflow of how this endpoint will behave depending on the inputs declared before:
graph TD;
Prefix(Prefix is extracted from URL) --> PrefixEnabled[[Is 'Prefix' enabled by config to create children]];
ParentBarcode(Parent barcode is extracted from URL arg) --> PrefixEnabled[[Is 'Prefix' enabled by config to create children]];
Child(Child is extracted from Parent barcode) --> PrefixEnabled[[Is 'Prefix' enabled by config to create children]];
PrefixEnabled -->|Yes|ValidBarcode[[Is 'Parent Barcode' a valid parent?]];
PrefixEnabled -->|No|Rejected([HTTP 422 - Rejected]);
ValidBarcode -->|Yes|ParentPresent[[Is 'Parent barcode' present in database?]];
ValidBarcode -->|No|NormalBarcode(Generate normal barcodes for the Prefix);
NormalBarcode -->NormalAccept([HTTP 201 - Created])
ParentPresent -->|Yes|ChildExist[[Do we have a value for 'Child'?]];
ParentPresent -->|No|ChildExist2[[Do we have a value for 'Child'?]];
ChildExist -->|Yes|ChildConstraint[[Is 'Child' bigger than the last child generated by the 'Parent barcode' in previous requests?]];
ChildConstraint -->|Yes|Impostor([HTTP 500 - Parent barcode is an impostor]);
ChildExist2 -->|Yes|Impostor;
ChildExist -->|No|ChildrenBarcodes(Generate children barcodes);
ChildrenBarcodes --> NormalAccept;
ChildConstraint -->|No|ChildrenBarcodes
ChildExist2 -->|No|ChildrenBarcodes;
If errors are experienced while pipenv attempts to install psycopg2
, try this:
LDFLAGS=`echo $(pg_config --ldflags)` pipenv install --dev
You can also try installing psycopg2
from the binary, which avoids the need for pg_config
locally.
To install psycopg2
as a binary, change the psycopg2
entry in the Pipfile to say psycopg2-binary
instead, and then run:
pipenv install psycopg2-binary
You can then run pipenv install --dev
again to get all the other dependencies installed.
This approach should allow you to install the postgres python driver (psycopg2) locally, without having a local copy of postgres. Use docker to run postgres as described in the 'Requirements for Development' section.
Don't commit your changes to the Pipfile or Pipfile.lock.
To update the table of contents after adding things to this README you can use the markdown-toc node module. To run:
npx markdown-toc --bullets="-" -i -- README.md