The project uses:
Python 3.9.1pip 20.2.3 (python 3.9)
pythonrequirements can be installed withpip install -r requirements.txt- the local server can be ran with
uvicorn app:app
- The current server is setup to run with heroku, and has both
Procfileandruntime.txtfiles - If deploying outside of heroku, the main script to run is
run.shwhich will install the dependencies (if any updates have occured) and run the production server- the core command to run the web server in production mode is:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker app:app --bind 0.0.0.0:$PORT --access-logfile - --log-level info- where
$PORTis the local open port set by the environment.
- the core command to run the web server in production mode is:
- Before deployment, make sure all of the environment variables for the project have been set (check the
Keys and variablessection of this document)
FastAPIoffers dynamic REST API documentation with swagger and redoc, using the openAPI spec.- these docs can be found in the
/docsand/redocendpoints respectively
- these docs can be found in the
- The auto-generated docs rely on
pydanticmodels for data types, and docstrings for each endpoint (held in thedocsfolder). Both of these are mentioned later on in theProject Structuresection in detail.
- A
pylintrcfile with the standard Google lint config is in the root folder and will provide detailed reports forpylint - to run the linter on ALL project files at once (useful for CI/CD pipelines and checks), use the
lint.shscript pytestis used to run all of the unit and integration tests for the project, all of which are located in thetests/folder
These are the modules that have most of the core logic and handle almost all of the code execution.
Modules here are grouped by their actions and their data so as to avoid table-sharing; providing the other modules with an interface for interacting with that data.
For example, if an endpoint for events (located in routes/events.py) needs to make a change to a user document in the database, it would have to go through the user interface laid out in utils/users.py.
This saves us the gigantic headache of coupled database tables, enabling us to change the internals of the modules as we please so long as we honor the interface we provide.
-
- Entry point for the actual routes that will be put on display through
FastAPI. Here, routes and sub-routers are added along with input and output doc strings, models, and other pieces of documentation that outline how the client interacts with the backend. - Not a ton of code should be here, just incoming data, calls to the appropriate handler in
utilsand then outgoing data. - If changes to the core procedure being laid out in the handling of a route are made, it is critical to update the
descriptionandsummary(declared in the@routerdecorators) as well to ensure up-to-date information reaches the documentation users. - For more info on the
@routerdecorators, or any otherFastAPIspecific info, refer to theFastAPIdocs
- Entry point for the actual routes that will be put on display through
-
-
Using a modeling framework like
pydantichelps tremendously in ensuring that the data coming in is valid and type secure.- Models can be pretty complex and have nested user-defined types, allowing the codebase to share a standard set of code and core models through inheritance.
- All
pydanticmodels importBaseModelfor some added niceties (e.g. direct typecasting todictsandjson)
-
There is one major problem with pydantic and pymongo:
- mongo relies on a unique
_idfield to be set either by the document, or by the database upon insertion. pydanticreads fields marked with a leading_as private/hidden and does not export them with exporting functions (such asmodel.dict())- to get around this problem, we use some utility functions and special classes (covered later)
- mongo relies on a unique
-
Some key notes on
pydanticandBaseModel:- Special model classes were made to represent data in the database as is.
- E.g.
models.users.User,models.events.Event, etc. - These models inherit from a modified version of
pydantic.BaseModel(located inmodels.commons) calledExtendedBaseModel.- This version of
BaseModelwill provide some extra features for it's children:- an auto-setter (upon instanciation) for the
_idfield that assigns a random uuid4 string to the field value - a
get_idfunction that is guaranteed to return a valid ID (this should be the only way to ever access a pydantic model's_idfield due to the mongo v. pydantic naming conflict) - an overriden
dict()method that fixes the_idproblem when casting the model to a python dict by callingmodel.dict()
- an auto-setter (upon instanciation) for the
- This version of
-
Little code should recide in these models outside of just classes. If a module has a recurring import or user defined type they can be put here as well to avoid repeating code.
-
-
- Files in
utilshold the interface for interacting with the database documents. - There are a handful of ulity functions that all either return, or operate on, a top-level model for the module
- e.g. most of the handlers in
utils.usersoperate on or return amodels.users.Userobject
- e.g. most of the handlers in
- All of the methods here should be
asyncto comply withFastAPI(it won't complain but it is a massive slowdown if we use mostly sync functions) - Helper methods and internals can be kept here as well, so long as the major outward module facing interface methods (e.g.
register_user) are kept simple and clean.
- Files in
Code here provides additional support to the main files or holds a small amount of semi-inelastic responsibilities that are easy to change but would have massive ripples accross the architecture if they weren't quarantined off.
-
- Holds simple strings with
FastAPIdocumentation. This eventually ends up in the generated endpoint docs so write as if you were consuming the routes yourself.
- Holds simple strings with
-
-
- High level app-wide configuration as well as middleware goes here. Shouldn't really need to be touched a lot.
- Also holds some key exports, namely
app, the mainFastAPIrouter object.
-
- Database configuration and instanciation. All instances and batch moves to the database are stored here, including helper methods for testing and constructors/destructors for the database instances.
-
Both of these modules, as well as all of the project-wide config, will be talked about in the next section
-
Being a monolithic project with lots of user based operations, there are many critical centers of code which can break the entire system if changed unwittingly. The next section will cover the most critical points of code which carry significant change that has effects on the whole system, and should be handled with the utmost care.
- the
config.mainmodule has two key jobs:- instanciate the global
FastAPIrouter instance that all subrouters get added to - read and export the global secret environment variables
- instanciate the global
- the newly instanciated
FastAPIrouter shouldn't be touched outside of the rootappmodule, or the tests module.
- Instanciates and sets up the database connection to the
mongoserver using a singleton pattern that only ever allows for a single client object to exist - Also handles the dynamic creation of testing databases and indexes
These modules need to be updated anytime data is updated elsewhere for the project to run
- the
appmodule assembles three key components of the project:- Adds the CORS hosts and origins to the
CORSMiddlewarehandler for the entire router instance- if frontend domain names OR backend server hosts change, this list of hosts/origins MUST be updated to avoid CORS errors
- Imports and adds all of the subrouters from the
routesmodule to the main router- if a new router is added, it must be imported and added here with an
app.include_routercall
- if a new router is added, it must be imported and added here with an
- Setting up documentation schema, logging, and other middleware
- Adds the CORS hosts and origins to the
There are two secret env vars that must be exported for the program to work on local/serverside deployments as well as CI/CD pipelines:
MONGO_DB_URI: the db URI (with authentication embedded)- current value:
mongodb+srv://sparkdev:[email protected]/?retryWrites=true&w=majority
- current value:
JWT_SECRET_KEY: the key used forJWTtoken creation- current value:
00cb508e977fd82f27bf05e321f596b63bf2d9f2452829e787529a52e64e7439
- current value:
lead backend maintainer: @astherath