All Projects → nuvic → flask_for_startups

nuvic / flask_for_startups

Licence: MIT license
Flask boilerplate using a services oriented structure

Programming Languages

python
139335 projects - #7 most used programming language
CSS
56736 projects
javascript
184084 projects - #8 most used programming language
HTML
75241 projects
Mako
254 projects
shell
77523 projects

Projects that are alternatives of or similar to flask for startups

cloudrun-fastapi
FastAPI on Google Cloud Run
Stars: ✭ 112 (-46.67%)
Mutual labels:  sqlalchemy, pytest
Websauna
Websauna is a full stack Python web framework for building web services and back offices with admin interface and sign up process
Stars: ✭ 259 (+23.33%)
Mutual labels:  sqlalchemy, pytest
Pytest Flask Sqlalchemy
A pytest plugin for preserving test isolation in Flask-SQLAlchemy using database transactions.
Stars: ✭ 168 (-20%)
Mutual labels:  sqlalchemy, pytest
agent-python-pytest
Framework integration with PyTest
Stars: ✭ 86 (-59.05%)
Mutual labels:  pytest
pytest-playwright
Pytest plugin to write Playwright tests with ease. Provides fixtures to have a page instance for each individual test and helpful CLI options for headless browsers.
Stars: ✭ 14 (-93.33%)
Mutual labels:  pytest
overhave
Web-framework for BDD: scalable, configurable, easy to use, based on Flask Admin and Pydantic.
Stars: ✭ 61 (-70.95%)
Mutual labels:  pytest
pytest-envvars
Pytest plugin to validate use of envvars on your tests
Stars: ✭ 21 (-90%)
Mutual labels:  pytest
fastapi-debug-toolbar
A debug toolbar for FastAPI.
Stars: ✭ 90 (-57.14%)
Mutual labels:  sqlalchemy
pytest-reverse
Pytest plugin to reverse test order.
Stars: ✭ 20 (-90.48%)
Mutual labels:  pytest
web-ui
python+selenium+pytest+allure UI 自动化框架
Stars: ✭ 199 (-5.24%)
Mutual labels:  pytest
Daisy-OLD
“ Hey there 👋 I'm Daisy „ PTB Group management bot with some extra features
Stars: ✭ 43 (-79.52%)
Mutual labels:  sqlalchemy
graphene-sqlalchemy-filter
Filters for Graphene SQLAlchemy integration
Stars: ✭ 117 (-44.29%)
Mutual labels:  sqlalchemy
sqlathanor
Serialization / De-serialization support for the SQLAlchemy Declarative ORM
Stars: ✭ 105 (-50%)
Mutual labels:  sqlalchemy
stock reminder bot
A twitter bot that reminds you of stock and crypto predictions
Stars: ✭ 25 (-88.1%)
Mutual labels:  pytest
sqlalchemy-utc
SQLAlchemy type to store aware datetime values
Stars: ✭ 87 (-58.57%)
Mutual labels:  sqlalchemy
FRDP
Boilerplate code for quick docker implementation of REST API with JWT Authentication using FastAPI, PostgreSQL and PgAdmin ⭐
Stars: ✭ 55 (-73.81%)
Mutual labels:  sqlalchemy
pytest-kind
Mirror of pytest-kind: Test your Python Kubernetes app/operator end-to-end with kind and pytest
Stars: ✭ 16 (-92.38%)
Mutual labels:  pytest
serialchemy
SQLAlchemy model serialization
Stars: ✭ 34 (-83.81%)
Mutual labels:  sqlalchemy
sqlalchemy exasol
SQLAlchemy dialect for EXASOL
Stars: ✭ 34 (-83.81%)
Mutual labels:  sqlalchemy
Semantic-Textual-Similarity
Natural Language Processing using NLTK and Spacy
Stars: ✭ 30 (-85.71%)
Mutual labels:  pytest

flask_for_startups

Code style: black

This flask boilerplate was written to help make it easy to iterate on your startup/indiehacker business, thereby increasing your chances of success.

Interested in learning how this works?

Want to show your support? Get me a coffee

Acknowledgements

Why is this useful?

When you're working on a project you're serious about, you want a set of conventions in place to let you develop fast and test different features. The main characteristics of this structure are:

  • Predictability
  • Readability
  • Simplicity
  • Upgradability

For side projects especially, having this structure would be useful because it would let you easily pick up the project after some time.

Features

How is this different from other Flask tutorials?

If you haven't read the above article, what's written here is a summary of the main points, and along with how it contrasts with the Flask structure from other popular tutorials.

To make it simple to see, let's go through the /register route to see how a user would create an account.

  • user goes to /register
    • flask handles this request at routes.py:
      • app.add_url_rule('/register', view_func=static_views.register)
    • you can see that the route isn't using the usual decorator @app.route but instead, the route is connected with a view_func (aka controller)
    • routes.py actually only lists these add_url_rule functions connecting a url with a view_func
    • this makes it very easy for a developer to see exactly what route matches to which view function since it's all in one file. if the urls were split up, you would have to grep through your codebase to find the relevant url
    • the view function in file static_views.py, register() simply returns the template
  • user enters information on the register form (register.html), and submits their info
  • their user details are passed along to route /api/register:
    • app.add_url_rule('/api/register', view_func=account_management_views.register_account, methods=['POST'])
    • here the view function in file account_management_views.py looks like this:
    def register_account():
      unsafe_username = request.json.get("username")
      unsafe_email = request.json.get("email")
      unhashed_password = request.json.get("password")
    
      sanitized_username = sanitization.strip_xss(unsafe_username)
      sanitized_email = sanitization.strip_xss(unsafe_email)
    
      try:
          user_model = account_management_services.create_account(
              sanitized_username, sanitized_email, unhashed_password
          )
      except marshmallow.exceptions.ValidationError as e:
          return get_validation_error_response(validation_error=e, http_status_code=422)
      except custom_errors.EmailAddressAlreadyExistsError as e:
          return get_business_requirement_error_response(
              business_logic_error=e, http_status_code=409
          )
      except custom_errors.InternalDbError as e:
          return get_db_error_response(db_error=e, http_status_code=500)
    
      login_user(user_model, remember=True)
    
      return {"message": "success"}, 201
    • it shows linearly what functions are called for this endpoint (readability and predictability)
    • the user input is always sanitized first, with clear variable names of what's unsafe and what's sanitized
    • then the actual account creation occurs in a service, which is where your business logic happens
    • if the account_management_services.create_account function returns an exception, it's caught here, and an appropriate error response is returned back to the user
    • otherwise, the user is logged in
  • so how does the account creation service work?
    def create_account(sanitized_username, sanitized_email):
        fields_to_validate_dict = {
            "username": sanitized_username,
            "email": sanitized_email,
            "password": unhashed_password,
        }
    
        AccountValidator().load(fields_to_validate_dict)
    
        if (
            db.session.query(User.email).filter_by(email=sanitized_email).first()
            is not None
        ):
            raise custom_errors.EmailAddressAlreadyExistsError()
    
        hash = bcrypt.hashpw(unhashed_password.encode(), bcrypt.gensalt())
        password_hash = hash.decode()
    
        account_model = Account()
        db.session.add(account_model)
        db.session.flush()
    
        user_model = User(
            username=sanitized_username,
            password_hash=password_hash,
            email=sanitized_email,
            account_id=account_model.account_id,
        )
    
        db.session.add(user_model)
        db.session.commit()
    
        return user_model
    • first, the user's info has to be validated through AccountValidator which checks for things like, does the email exist?
    • then it checks whether the email exists in the database, and if so, raise a custom error EmailAddressAlreadyExists
    • otherwise, it will add the user to the database and return the user_model
    • notice how the variable is called user_model instead of just user, making it clear that it's an ORM representation of the user
  • how do these custom errors work?
    • so if a user enters a email that already exists, it will raise this custom error from custom_errors.py
    class EmailAddressAlreadyExistsError(Error):
        message = "There is already an account associated with this email address."
        internal_error_code = 40902
    • the message is externally displayed to the user, while the internal_error_code is more for the frontend to use in debugging. it makes it easy for the frontend to see exactly what error happened and debug it (readability)
    def get_business_requirement_error_response(business_logic_error, http_status_code):
        resp = {
            "errors": {
                "display_error": business_logic_error.message,
                "internal_error_code": business_logic_error.internal_error_code,
            }
        }
        return resp, http_status_code
    • error messages are passed back to the frontend via a similar format as above: display_error and internal_error_code. the validation error message will be different in that it has field errors. (simplicity)
  • Testing
    • the tests are mostly integration tests using a test database
    • more work could be done here, but each endpoint should be tested for: permissions, validation errors, business requirement errors, and success conditions

Setup Instructions

Change .sample_flaskenv to .flaskenv

Database setup

Databases supported:

  • PostgreSQL
  • MySQL
  • SQLite

However, I've only tested using PostgreSQL.

Replace the DEV_DATABASE_URI with your database uri. If you're wishing to run the tests, update TEST_DATABASE_URI.

Repo setup

  • git clone [email protected]:nuvic/flask_for_startups.git
  • sudo apt-get install python3-dev (needed to compile psycopg2, the python driver for PostgreSQL)
  • If using poetry for dependency management
    • `poetry install
  • Else use pip to install dependencies
    • python3 -m venv venv
    • activate virtual environment: source venv/bin/activate
    • install requirements: pip install -r requirements.txt
  • rename .sample_flaskenv to .flaskenv and update the relevant environment variables in .flaskenv
  • initialize the dev database: alembic -c migrations/alembic.ini -x db=dev upgrade head
  • run server:
    • with poetry: poetry run flask run
    • without poetry: flask run

Updating db schema

  • if you make changes to models.py and want alembic to auto generate the db migration: `./scripts/db_revision_autogen.sh "your_change_here"
  • if you want to write your own changes: ./scripts/db_revision_manual.sh "your_change_here" and find the new migration file in migrations/versions

Run tests

  • if your test db needs to be migrated to latest schema: alembic -c migrations/alembic.ini -x db=test upgrade head
  • python -m pytest tests

Dependency management

Using poetry.

Activate poetry shell and virtual environment:

  • poetry shell

Check for outdated dependencies:

  • poetry show --outdated

Other details

  • Sequential IDs vs UUIDs?
    • see brandur's article for a good analysis of UUID vs sequence IDs
    • instead of UUID4, you can use a sequential UUID like a tuid
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].