Skip to main content

splent_feature_profile

The profile feature of a SPLENT product line.

Every account has a profile with a name and a surname. The person edits it at /profile/edit, and a client reads it as JSON at /api/v1/profile/me. Other features extend all three without touching profile's code. They add fields to the edit form, sections below it and keys to the JSON. It needs auth, which owns the account. With signup in the product, the sign-up asks for the name and surname as well.

A SPLENT feature is a Python package that a product composes with others. Its models, routes, services, templates and hooks are registered when the product starts. What this feature provides and requires is listed in the [tool.splent.contract] section of pyproject.toml, which splent feature:contract --write keeps in sync with the source.

A profile for every account

auth sends user-registered when an account is opened through AuthenticationService.create_user, as the sign-up and the external sign-in features do, and profile creates its UserProfile then. The name and surname come with the signal when the sign-up asked for them, and the profile is empty otherwise.

An account opened without that signal, from auth's accounts screen, with its create-user command or by a seeder, has no profile at first. It gets an empty one the first time it is needed, when the person opens the edit page or a client asks /api/v1/profile/me, and the person fills it in on the edit page. An account that already has a profile keeps it.

Name and surname at sign-up

When the product has signup, profile registers two fields on its form with register_signup_field, name at order 10 and surname at order 20, so both are asked for before the address. Both are required. signup is optional, and without it nothing is registered. A signup older than register_signup_field (0.2.0 and before) asks for no name, and the product starts all the same.

Fields other features add

A feature that keeps more about the person asks for it on the edit page with register_profile_field. The field is drawn on the page, validated with the form, filled from the profile attribute of the same name and saved onto it when UserProfile has a column of that name. The feature that registers the field adds the column, with a declared model extension of UserProfile and a migration of its own.

# __init__.py of a feature that extends UserProfile with an affiliation
from flask_babel import lazy_gettext as _l
from wtforms import StringField
from wtforms.validators import Length, Optional

from splent_io.splent_feature_profile.forms import register_profile_field


def init_feature(app):
    register_profile_field(
        "affiliation",
        StringField(_l("Affiliation"), validators=[Optional(), Length(max=255)]),
    )

order places the field. Lower comes first.

Field Order
A registered field that says so its own
name 100 (NAME_ORDER)
surname 200 (SURNAME_ORDER)
A registered field that does not say 300 (DEFAULT_FIELD_ORDER)

The registry is read when a request needs the form, so a field registered after profile has loaded is asked for too. The form class is built once for each set of registered fields and reused while that set stays the same. Registering a name again replaces the earlier field, and remove_profile_field(name) stops asking for one. A field's own render_kw (a placeholder) and description (a line of help) are shown. A field without a column is validated and drawn, and profile does not store it.

Some names are refused with a ValueError when the field is registered. id, user_id and user say whose profile it is, which is never the person's to edit. The form's own fields (name, surname, submit, csrf_token) and the form's attributes (data, errors, meta...) cannot be taken either.

Sections below the form

The edit page opens the slot profile.edit.sections, right below the profile form. Linked accounts, notification preferences and the like go there. Each section is a block of its own, outside the profile form, so it can carry forms of its own. A callback takes no arguments and returns HTML, or an empty string to add nothing on this request. When no callback returns anything, nothing is drawn.

# hooks.py of a feature that links external accounts
from flask import render_template
from splent_framework.hooks.template_hooks import register_template_hook


def linked_accounts():
    return render_template("hooks/linked_accounts.html")


register_template_hook("profile.edit.sections", linked_accounts, order=10)

The profile as JSON

GET /api/v1/profile/me answers the signed-in person's profile.

{"name": "Ada", "surname": "Lovelace", "email": "ada@example.com"}

Nobody signed in gets 401, as JSON with an error key, so a page that calls it with fetch never receives the login page instead. An account without a profile gets an empty one, and answers with empty name and surname. Every answer carries Cache-Control: no-store, so no browser or proxy keeps one person's data for the next.

A feature adds its own keys with register_profile_payload. The function takes the user and their profile and returns a dict, and runs on every request.

from splent_io.splent_feature_profile.payload import register_profile_payload


def affiliation(user, profile):
    return {"affiliation": profile.affiliation}


def init_feature(app):
    register_profile_payload(affiliation)

The functions run in the order they were registered, so a later one wins a key two of them send. name, surname and email always hold the profile's and the account's own values. Registering a function again does nothing, and remove_profile_payload(fn) takes one out.

Translations

The edit page, its form and the sign-up fields ship with a Spanish catalog.

Use it in a product

splent feature:add splent_feature_profile

Develop

splent feature:test splent_feature_profile
splent feature:contract splent_feature_profile

Release

splent feature:release splent_feature_profile --dry-run --bump patch
splent feature:release splent_feature_profile

The first command rehearses the release and changes nothing. The second publishes it to the git forge and to PyPI, and a PyPI version is permanent.

Metadata

Release files for splent-feature-profile 1.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for splent-feature-profile 1.6.0
File Size Uploaded Provenance Actions
splent_feature_profile-1.6.0.tar.gz 30.6 kB Without provenance Details for splent_feature_profile-1.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for splent-feature-profile 1.6.0
File Interpreter ABI Platform Actions
splent_feature_profile-1.6.0-py3-none-any.whl Python 3 none any Details for splent_feature_profile-1.6.0-py3-none-any.whl

Total release size: 67.5 kB

Release files / splent_feature_profile-1.6.0.tar.gz

Download URL splent_feature_profile-1.6.0.tar.gz
Size 30.6 kB
Tags Source
SHA-256 checksum
How to use checksums
53e4f86f8950d25530af24302131792023e3514b4a042d33da989d61ac650088
BLAKE2b-256 checksum
How to use checksums
f4e37f584205cb4f655b20881f2270fb94bec3f40e45c8eabc43a2f20effb8db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.13

Release files / splent_feature_profile-1.6.0-py3-none-any.whl

Download URL splent_feature_profile-1.6.0-py3-none-any.whl
Size 36.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
316c86f90e413b3b180744ec647d51504062e6d779844a1999de1e31a5a5f59b
BLAKE2b-256 checksum
How to use checksums
9b62e67e6ad5458d51df2bd7f6f2765290873fa838647da95429429d112bba8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.13

Security for splent-feature-profile 1.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page