This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Architecture

Implementation and system design information that potential project contributors can consult.

1 - Translation (i18n)

How to translate Subscribie into different languages & update translations.

Prerequisite

You must run Subscribie, see running Subscribie locally.

After Subscribie is set-up locally, you can stop Subscribie (ctrl + c) then view the translation commands:

export FLASK_APP=subscribie
flask translate --help
Usage: flask translate [OPTIONS] COMMAND [ARGS]...

  Translation and localization commands.

Options:
  --help  Show this message and exit.

Commands:
  compile  Compile all languages.
  init     Initialize a new language.
  update   Update all languages.

Steps to update translations

When new text gets added to Subscribie, it needs to be translated. For example:

Before marked for translation:

@app.route("/new-route")
def my_new_route():
    return "This is my new route"

After marked for translation:


from flask_babel import _

@app.route("/new-route")
def my_new_route():
    return _("This is my new route")

warning Make sure you didn’t miss above, the _ is a function, from the flask_babel library. babel uses _ to help find all the translatable text.

For more complex translation markets in Jinja2 templates see examples:

The main steps to perform static translation in Subscribie are:

  1. Update the code replacing hard-coded strings with the _ function
  2. Running flask translate update
  3. Edit the updated .po file with translations (e.g. subscribie/subscribie/translations/de/LC_MESSAGES/messages.po)
  4. Running flask translate compile, which generates a speed optimised translation file (e.g. subscribie/subscribie/translations/de/LC_MESSAGES/messages.po)
  5. Test the site flask run, commit the changes and raise a pull request.

Credits

The Subscribie i18n translation process was expedited thanks to Miguel Grinberg who wrote an article the-flask-mega-tutorial-part-xiii-i18n-and-l10n, and is the author of Flask Web Development, 2nd Edition

2 - Testing

How to run Subscribie tests

Subscribie uses Playwright for automated browser testing which tests most features at every pull request.

There are two types of test Subscribie has:

  1. Browser automated tests using playwright
  2. Basic Python tests

Where are the tests?

Tests are in the tests directory of the Subscribie repo

Run Basic Tests with pytest

. venv/bin/activate # activates venv
python -m pytest --ignore=node_modules # run pytest

Run a single test

python -m pytest -vv -k 'test_create_PriceList_and_price_list_rule_percent_discount'

Browser automated tests with playwright

Setup Playwright

Install Playwright dependencies

npm install
npm i -D @playwright/test
npx playwright install
npx playwright install-deps

If you see: UnhandledPromiseRejectionWarning: browserType.launch: Host system is missing dependencies!

  Install missing packages with:
      sudo apt-get install libgstreamer-plugins-bad1.0-0\
          libenchant1c2a

Listen for Stripe webhooks locally

Stripe webhooks are recieved when payment events occur. The test suite needs to listen to these events locally when running tests.

tldr:

  1. Install the Stripe cli
  2. Run stripe listen --events checkout.session.completed,payment_intent.succeeded,payment_intent.payment_failed,payment_intent.payment_failed --forward-to 127.0.0.1:5000/stripe_webhook

For testing failed payments using test cards table, the test card 4000000000000341 is especially useful because the cards in the previous table can’t be attached to a Customer object, but 4000000000000341 can be (and will fail which is useful for testing failed subscription payments such as insufficient_funds).

Stripe Webhooks

Stripe takes payments. Stripe sends payment related events to Subscribie via POST requests- also known as ‘webhooks’). If you’re doing local development, then you need Stripe to send you the test payment events you’re creating. stripe cli is a tool created by Stripe to do that.

  1. Install Stripe cli
  2. Login into stripe via stripe login (this shoud open the browser with stripe page where you should enter your credentials). If this command doesn’t work use stripe login -i (this will login you in interactive mode where instead of opening browser you’ll have to put Stripe secret key directly into terminal)
  3. Run
stripe listen --events checkout.session.completed,payment_intent.succeeded,payment_intent.payment_failed --forward-to 127.0.0.1:5000/stripe_webhook

You will see:

⢿ Getting ready... > Ready! 
  1. Please note, the stripe webhook secret is not needed for local development - for production, Stripe webhook verification is done in Stripe-connect-webhook-endpoint-router (you don’t need this for local development).
stripe listen --events checkout.session.completed,payment_intent.succeeded --forward-to 127.0.0.1:5000/stripe_webhook

Remember Stripe will give you a key valid for 90 days, if you get the following error you will need to do step 2 again:

Error while authenticating with Stripe: Authorization failed, status=401

Bulk delete Stripe accounts

Warning Stripe webhooks will be automatically disabled if error rates go above a certain %. To delete in bulk test Stripe Connect express accounts see: ./tests/delete_stripe_accounts_bulk.py

Run Playwright tests

Important: Stripe cli must be running locally to recieve payment events: stripe listen --events checkout.session.completed,payment_intent.succeeded --forward-to 127.0.0.1:5000/stripe_webhook


Stripe-connect-account-announcer needs to be running locally if you’re runnning browser automated tests locally.

Turn on headful mode & set Playwright host

export PLAYWRIGHT_HEADLESS=false
export PLAYWRIGHT_HOST=http://127.0.0.1:5000/
export SUBSCRIBER_EMAIL_USER=test@example.com

Run playwright tests:

There’s two key ways to run tests:

  1. As fast as possible (in parallel, where possible via the DAG) of tests
  • This is whats done in CI to make pull request feedback as fast as possible, whilst running all tests
  1. Running an individual test
  • This is useful for debugging, such as during development of a new test
Run all tests in parallel
. venv/bin/activate # activate python virtual environment
cd tests/browser-automated-tests-playwright
python run-playwright-tests.py
Run an individual test
cd tests/browser-automated-tests-playwright
# Run the test '@133_shop_owner_order_plan_with_cooling_off_period' in debug mode
npx playwright test --debug --headed --grep @133_shop_owner_order_plan_with_cooling_off_period
# Run test @stripe_connect in debug mode:
npx playwright test --debug --headed --grep @stripe_connect

Something not working? Debug playwright tests with the playwright inspector

PWDEBUG=1 npx playwright test

If you don’t see the playwright inspector, make sure you have an up to date version of playwright.

Alternative debugging with breakpoints

  • Set breakpoint(s) by typing debugger; anywhere you want a breakpoint in a test. Then run with the node debugger active:
unset PWDEBUG
node inspect index.js

Useful node debug commands:

  • help # shows help
  • n # go to next line
  • list() # show code where paused
  • cont # continue execution until next breakpoint

Advanced Playwright Testing

You can run individual playwright tests by:

  • Tags: @ are used to run tests by issue number
  • User based (shop-owner, subscriber)
  • Test name

For example, you may want to run only tests which impact the shop-owner

The test file name structure is Worker Name--Issue Number-User Based-Test Name

Examples to run a specific Playwright test:

npx playwright test --grep @issue-numer/User Based/Test Name --update-snapshots

For example:

Run only tests for issue #704

npx playwright test --grep @704 --update-snapshots

Run all subscriber tests:

npx playwright test --grep @subscriber --update-snapshots

Run only plan creation tests:

npx playwright test --grep @plan_creation --update-snapshots

To exclude specific test, but run all others:

npx playwright test --grep-invert @issue-numer/User Based/Test Name --update-snapshots.

How playwright workers work

The workers are specified in the playwright.config.ts file. Which is the file containing the global settings for the playwright tests:

  • The working directories
  • Global time out 3mins before each test is retried
  • Retry 2 times if a test fails
  • How many workers(parallel jobs playwright can do) at the same time. We have 3
  • Headless config on/off depending in the .env variable if true or false
    • SlowMo specified in the env (miliseconds)
    • baseURL, specified in the env. Ex 127.0.0.1
    • Video Recording

Using Chromium because headless only works in chromium not mobile

Worker 1 is called index.spec.js (it can be called any name but needs to end in .spec.)

In worker 1 we have these tests:

  • Before each test, playwright logs in with a shop-owner user.

  • Stripe test: Check if Stripe is connected, otherwise connect to Stripe. (this is the longest tests 3minutes if not connected)

  • order_plan_with_only_recurring_charge

  • order_plan_with_only_upfront_charge

  • Order_plan_with_free_trial

    • Order_plan_with_subscription_and_upfront_charge

    • Transaction filter by name and plan title

    • pause, resume subscription test

    • cancel subscription test.

    • Subscriber filter by name and plan title

Worker2 is called worker2.spec.js and in this worker have these tests:

  • plan_creation

  • changing_plans_order

  • share_private_plan_url

  • order_plan_with_choice_options_and_required_note

  • order_plan_with_cancel_at

  • order_plan_cooling_off

Why these tests? plan creation, changing plans order, and sharing private plan url do not depend on the previous tests so while Worker 1, Stripe Connect, test is running (takes 3min to complete) this worker is running those 3 tests first. By the time those 3 tests are run, tripe is already connected and we can start doing tests that require Stripe to be connected like ordering plans.

Worker 3 is called worker3.spec.js and this worker has these tests:

  • clear_DB

  • categories_creation

  • private_page_creation

  • Public_page_creation

  • Slogan_creation

  • Change_shop_colour

  • Adding_vat

  • ordering_plan_with_VAT

  • subscriber_magic_login

  • subscriber_order_free_plan

  • subscriber_change_card_details

Worker 3 mostly contains tests that don’t require Stripe to be connected, only the 4th last ones requires Stripe to be connected.

The worker name example, test.spec, worker1.spec, worker2.spec.

The spec is necessary for the tests to be discovered by Playwright as another worker.

To run only the specified tests, the syntax is:

TEST RELATIONSHIP

Subscribie Playwright tests drawio

3 - Events & Signals

Subscribie emits Signals when an Event occurs, such as signal_payment_failed. This is achieved using the python blinker library.

How Signals & Events work together

When an ’event’ happens, you find out by receiving a signal- but you must subscribie to the signals you’re interested in.

For a more in depth generic explanation of signals see the official blinker documentation.

Subscribie Signals

By convention, signal names are prefixed with signal_

Subscribie has the following signals (see signals.py for most up to date list:

  • signal_journey_complete
  • signal_payment_failed
  • signal_new_subscriber
  • signal_new_donation (see donations)

Structure:

  • signals.py is where signals are defined
  • receivers.py is where signals are connected to recievers
  • notifications.py & email.py

How do I create an event?

It’s helpful to think in terms of Signals which emit notifications to all connected recievers. Many Receivers may be interested in a single event. For example, a new order your email notification system might be interested, plus your postal service. One Signal can have multiple Receivers connected to it.

First, you must have created a signal.

How are events (signals) fired?

Events are ‘fired’ when send is called on the signal. For example, send() is called on the journey_complete signal when a subscriber gets to the ’thank you’ page. Any recievers connected to that signal withh receive that event.

Note in the example below, we are passing the current app, and the associated email address for the event. Be careful doing this if your reciever runs in a background thread, as that will not have access to the application context.

journey_complete.send(current_app._get_current_object(), email=email)

4 - Logging

How to view Subscribie logs during development / testing / production.

Logging

Subscribie using python standard logging module, with log handlers configred for stdout and Telegram.

tldr: Set PYTHON_LOG_LEVEL=DEBUG in your .env settings file.

Viewing logs & Changing the Log level

In the file .env, set the PYTHON_LOG_LEVEL to DEBUG or lower.

PYTHON_LOG_LEVEL=DEBUG

Then re-start Subscribie.

Loglevel options are:

  • DEBUG
  • INFO
  • WARNING
  • ERROR
  • CRITICAL

See also: When to use logging

Telegram logging

In the same way, you can configure telegram log level verbosity by setting TELEGRAM_PYTHON_LOG_LEVEL in your .env settings file.

TELEGRAM_PYTHON_LOG_LEVEL=ERROR

Logging code

Where is the logger setup?

See logger.py

Common Python logging mistakes

I see no logs, even though I’m doing log.warning etc

If your log PYTHON_LOG_LEVEL is too high, e.g. if set to ERROR, then the logger (or more precisely, a given log handler, won’t display any log messages lower than ERROR.

Thinking there is only “one” python logger

Python logging has two key concepts: The logger, and log handlers. In Subscribie, we log to stdout using the built-in StreamHandler and use an additonal built-in handler QueueHandler which is configured to send to Telegram if configured.

See logger.py for implementation.

See also

How to send telegram messages with python tutorial

5 - Webhooks

How to configure webhooks to integrate Subscribie with your other applications.

Register your webhook endpoint via an API request

  1. Authenticate with Subscribie
  2. Send a POST request with your webhook endpoint address (see example below)