Missions example
The documentation is under heavy development!
The documentation is actively being developed and might not cover every feature. For full details, refer to the source code.
Overview
The missions example demonstrates how to build a full-featured application using Plateforme's resource management capabilities. This project showcases a space mission management system with multiple interconnected packages handling different aspects of space operations.
Getting started
The missions example can be run using the Plateforme built-in CLI:
# Build the application and initialize the database
plateforme -p src/examples/missions build
# Start the application server
plateforme -p src/examples/missions start
The application will be available at http://127.0.0.1:8001
.
Project structure
The project implements a modular architecture through three packages: assets
, ops
, and staff
. Each package manages a specific domain of space missions, such as physical resources, mission operations, and personnel.
missions/
├── client/ # API request examples
├── packages/
│ ├── assets/ # Physical resource management
│ ├── ops/ # Mission operations
│ └── staff/ # Personnel management
├── server/
│ ├── main.py # Application entry point
│ └── setup.py # Database initialization
└── pyproject.toml # Project configuration
Project configuration
The project is configured through pyproject.toml
, which defines both the project metadata and application settings:
[project]
name = "missions"
version = "0.1.0"
description = "The missions example project"
keywords = ["plateforme", "missions", "example"]
[tool.plateforme.apps.default]
scripts = {setup = "python server/setup.py"}
build = ["setup"]
start = "server.main:app --port 8001"
The configuration consists of two main sections. The project section defines basic metadata including the package name, version, and keywords. This information helps identify and categorize the project within a larger ecosystem.
The plateforme.apps.default
section configures the application's build and runtime behavior. It specifies that the build process should run the setup
script, which initializes the database with sample data. The start
configuration indicates that the application should run on port 8001
using the main.py
entry point.
Application configuration
The application uses Plateforme's main entry point to configure and connect all components:
from plateforme import Plateforme
# Create application
app = Plateforme(
debug=True, # (1)!
logging={'level': 'DEBUG'}, # (2)!
database_engines='plateforme.db', # (3)!
namespaces=[ # (4)!
('assets', {'alias': ''}),
('staff', {'alias': ''}),
],
packages=[ # (5)!
'packages.assets',
'packages.ops',
'packages.staff',
],
)
- Enable debug mode for development.
- Configure logging to output debug messages. Advanced configuration can be provided to customize logging behavior.
- Define the database connection as a simple SQLite engine. Advanced configuration can be provided to specify multiple database engines and custom connection settings.
- Configure namespaces for the packages. Setting an empty alias allows accessing resources endpoints directly without a prefix.
- Register the packages to include within the application.
This configuration enables debug mode for development, sets up logging, and configures the database connection. It also registers the required packages and their namespaces, establishing the foundation for the modular architecture described below.
Application build
The application build process is configured within the pyproject.toml
file to run the setup.py
script. The script initializes the database with sample data:
...
# Create database
app.metadata.create_all()
# Create fake data
fake = Faker()
# Create fake materials
materials = [
Material(
code=str(random.randint(1, 9999999999)),
name=fake.name(),
description=fake.sentence(),
price=random.uniform(100000, 100000000),
)
for _ in range(random.randint(1, 100))
]
...
Source code in src/examples/missions/server/setup.py
import logging
import random
from faker import Faker
from server.main import app
from packages.assets import Material, Rocket, RocketPart
from packages.ops import Mission, Station
from packages.staff import Astronaut, Crew
logging.getLogger('faker').setLevel(logging.ERROR)
# Create database
app.metadata.create_all()
# Create fake data
fake = Faker()
# Create fake materials
materials = [
Material(
code=str(random.randint(1, 9999999999)),
name=fake.name(),
description=fake.sentence(),
price=random.uniform(100000, 100000000),
)
for _ in range(random.randint(1, 100))
]
# Create fake rockets
rockets = [
Rocket(
code=str(random.randint(1, 9999999999)),
name=fake.name(),
description=fake.sentence(),
parts=[
RocketPart(
code=f"{(count * 10):04d}",
material=material,
quantity=random.randint(1, 10),
)
for count, material in enumerate(
random.sample(materials, random.randint(1, 10))
)
],
)
for _ in range(random.randint(1, 100))
]
# Create fake astronauts
astronauts = [
Astronaut(
code=str(random.randint(1, 9999999999)),
name=fake.name(),
role=random.choice([
'doctor', 'engineer', 'pilot', 'technician'
]),
)
for _ in range(random.randint(1, 100))
]
# Create fake crews
crews = [
Crew(
code=str(random.randint(1, 9999999999)),
name=fake.word().capitalize(),
astronauts=random.sample(astronauts, random.randint(2, 10)),
)
for _ in range(random.randint(1, 10))
]
# Assign lead to crews
for crew in crews:
crew.lead = random.choice(crew.astronauts)
# Create fake stations
stations = [
Station(
code=str(random.randint(1, 9999999999)),
name=fake.city(),
description=fake.sentence(),
coordinates=str(fake.local_latlng(coords_only=True)),
)
for _ in range(random.randint(1, 100))
]
# Create fake missions
missions = [
Mission(
code=str(random.randint(1, 9999999999)),
name=fake.word().capitalize(),
description=fake.sentence(),
crew=random.choice(crews),
rocket=random.choice(rockets),
station=random.choice(stations),
launch_date=fake.date_this_decade(),
)
for _ in range(random.randint(1, 100))
]
# Commit fake data
with app.session() as session:
session.add_all([
*astronauts,
*crews,
*materials,
*rockets,
*stations,
*missions,
])
session.commit()
session.close()
# Generate summary
print('Mock data generated:')
print('- Astronauts:', len(astronauts))
print('- Crews:', len(crews))
print('- Materials:', len(materials))
print('- Rockets:', len(rockets))
print('- Stations:', len(stations))
print('- Missions:', len(missions))
Implementation
Each package is defined with a configuration file that includes the package's information and default namespace and API settings. For example, the assets package configuration is defined as follows:
[plateforme]
name = "assets"
version = "0.1.0"
description = "The missions example assets package"
keywords = ["plateforme", "example", "missions", "assets"]
[plateforme.package]
namespace = "assets"
api_services = false
Assets package
The assets package manages physical resources required for space missions. It defines two primary resources for managing materials and rockets, where materials are basic components used in rockets:
from plateforme import CRUDResource, Field
class Material(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
description: str | None = Field(default=None, max_length=1000)
price: float = Field(gt=0)
Rockets are assembled from multiple parts linked to materials:
from plateforme import BaseResource, CRUDResource, ConfigDict, Field
# TODO: from plateforme.schema import computed_field
from .materials import Material
class Rocket(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
description: str | None = Field(default=None, max_length=1000)
parts: list['RocketPart'] = Field(default_factory=list)
# TODO: @computed_field
# TODO: @property
# TODO: def price(self) -> float:
# TODO: return sum(part.material.price * part.quantity for part in self.parts)
class RocketPart(BaseResource):
__config__ = ConfigDict(
indexes=[
{'rocket', 'code'},
{'rocket', 'material'}
],
)
code: str = Field(max_length=10)
rocket: Rocket
material: Material
quantity: int
Operations package
The operations package handles mission planning and space station management. Stations provide mission destinations:
import typing
from plateforme import CRUDResource, Field
if typing.TYPE_CHECKING:
from .missions import Mission
class Station(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
description: str | None = Field(default=None, max_length=1000)
coordinates: str | None = Field(default=None)
missions: list['Mission'] = Field(default_factory=list)
Missions coordinate crews, rockets, and stations:
import datetime
from plateforme import CRUDResource, Field
from ..assets import Rocket
from ..staff import Crew
from .stations import Station
class Mission(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
description: str | None = Field(default=None, max_length=1000)
crew: Crew
rocket: Rocket
station: Station
launch_date: datetime.date = Field(default_factory=datetime.date.today)
Staff package
The staff package manages personnel and crew assignments. It defines astronaut profiles:
import typing
from typing import Literal
from plateforme import CRUDResource, Field
if typing.TYPE_CHECKING:
from .crews import Crew
class Astronaut(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
role: Literal['doctor', 'engineer', 'pilot', 'technician']
crews: list['Crew'] = Field(default_factory=list)
And organizes them into crews:
from typing import Self
from plateforme import CRUDResource, Key, Field, route
from .astronauts import Astronaut
class Crew(CRUDResource):
code: str = Field(unique=True, max_length=10)
name: str = Field(max_length=100)
astronauts: list[Astronaut] = Field(
default_factory=list,
rel_load='selectin',
)
lead: Astronaut | None = Field(
default=None,
init=False,
association_alias='crew_lead',
)
@route.post()
async def assign_lead(self, astronaut: Key[Astronaut]) -> Self:
lead = await astronaut.resolve()
if lead not in self.astronauts:
raise ValueError('The lead must be part of the crew')
self.lead = lead
return self
Playground
Managing rockets
The system provides comprehensive rocket management capabilities.
Add some rockets
POST http://127.0.0.1:8001/assets/rockets HTTP/1.1
content-type: application/json
{
"payload": [
{ "code": "SAT-V", "name": "Saturn V" },
{ "code": "ARI-5", "name": "Ariane 5" }
],
"dry_run": true
}
Fetch rockets
GET http://127.0.0.1:8001/assets/rockets?.id=in~3,4 HTTP/1.1
content-type: application/json
{
"include": {
"__all__": {
"id": true,
"code": true,
"parts": {
"__all__": {
"id": true,
"material": {
"id": true,
"code": true
},
"quantity": true
}
}
}
}
}
Response
[
{
"id": 3,
"code": "9506131347",
"parts": [
{
"id": 21,
"material": {
"id": 17,
"code": "9627606784"
},
"quantity": 5
},
{
"id": 20,
"material": {
"id": 23,
"code": "9070371458"
},
"quantity": 9
},
{
"id": 19,
"material": {
"id": 27,
"code": "1926426509"
},
"quantity": 2
},
{
"id": 15,
"material": {
"id": 30,
"code": "9128315132"
},
"quantity": 1
},
{
"id": 17,
"material": {
"id": 42,
"code": "5479104904"
},
"quantity": 4
},
{
"id": 16,
"material": {
"id": 49,
"code": "9598485656"
},
"quantity": 7
},
{
"id": 18,
"material": {
"id": 51,
"code": "4039707946"
},
"quantity": 7
}
]
},
{
"id": 4,
"code": "2734370508",
"parts": [
{
"id": 22,
"material": {
"id": 6,
"code": "2587569535"
},
"quantity": 4
}
]
}
]
Update rockets
PATCH http://127.0.0.1:8001/assets/rockets?.code=like~*2 HTTP/1.1
content-type: application/json
{
"payload": {
"description": null
},
"include": {
"__all__": {
"id": true,
"code": true,
"description": true
}
}
}
Response
[
{
"id": 10,
"code": "4760719322",
"description": null
},
{
"id": 18,
"code": "3142734512",
"description": null
},
{
"id": 19,
"code": "7177675612",
"description": null
},
{
"id": 21,
"code": "9112122",
"description": null
},
{
"id": 68,
"code": "9010040722",
"description": null
},
{
"id": 79,
"code": "8145379582",
"description": null
}
]
Upsert rockets
PUT http://127.0.0.1:8001/assets/rockets HTTP/1.1
content-type: application/json
{
"payload": {
"code": "ARI-6",
"name": "Ariane 6",
"description": "European launch vehicle."
}
}
Delete rockets
DELETE http://127.0.0.1:8001/assets/rockets/85 HTTP/1.1
content-type: application/json
{
"return_result": true
}
Managing crews
The staff package enables comprehensive crew management.
Assign crew leads
POST http://127.0.0.1:8001/staff/crews/1/assign-lead HTTP/1.1
content-type: application/json
"2"
Response
{
"id": 1,
"type": "crew",
"code": "881557580",
"name": "Base",
"astronauts": [
{
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
},
{
"id": 4,
"type": "astronaut",
"code": "9889304965",
"name": "Linda Fernandez",
"role": "pilot"
},
{
"id": 3,
"type": "astronaut",
"code": "5054931889",
"name": "Jack Rogers",
"role": "technician"
}
],
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
}
Filter crews
GET http://127.0.0.1:8001/staff/crews?.astronauts*.id=ge~1;le~15&sort=+name HTTP/1.1
content-type: application/json
{
"include": {
"__all__": {
"id": true,
"code": true,
"name": true,
"lead": true
}
}
}
Response
[
{
"id": 1,
"code": "881557580",
"name": "Base",
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
},
{
"id": 2,
"code": "5167989951",
"name": "Serious",
"lead": {
"id": 7,
"type": "astronaut",
"code": "3717936433",
"name": "Joshua Santos",
"role": "technician"
}
}
]
Planning missions
The operations package facilitates mission planning and execution.
Query missions
GET http://127.0.0.1:8001/missions?sort=-id&page_size=10&page=2 HTTP/1.1
content-type: application/json
{
"filter": {
"id": "ge~1;le~30",
"crew.lead.id": "in~1,2,3"
},
"include": {
"__all__": {
"id": true,
"code": true,
"name": true,
"lead": true,
"crew": {
"id": true,
"code": true,
"lead": true
},
"rocket": {
"id": true,
"code": true
},
"station": {
"code": true,
"name": true,
"coordinates": true
},
"launch_date": true
}
}
}
Response
[
{
"id": 6,
"code": "2993145882",
"name": "Central",
"crew": {
"id": 1,
"code": "881557580",
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
},
"rocket": {
"id": 38,
"code": "4545618943"
},
"station": {
"code": "4183126061",
"name": "Matthewburgh",
"coordinates": "('33.7207', '-116.21677')"
},
"launch_date": "2022-11-27"
},
{
"id": 5,
"code": "2725533067",
"name": "Cultural",
"crew": {
"id": 1,
"code": "881557580",
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
},
"rocket": {
"id": 50,
"code": "743920576"
},
"station": {
"code": "4183126061",
"name": "Matthewburgh",
"coordinates": "('33.7207', '-116.21677')"
},
"launch_date": "2025-01-02"
},
{
"id": 3,
"code": "1971626508",
"name": "Push",
"crew": {
"id": 1,
"code": "881557580",
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
},
"rocket": {
"id": 20,
"code": "1117447911"
},
"station": {
"code": "917844342",
"name": "Lake Jeff",
"coordinates": "('33.76446', '-117.79394')"
},
"launch_date": "2024-04-23"
},
{
"id": 2,
"code": "7593017353",
"name": "Professional",
"crew": {
"id": 1,
"code": "881557580",
"lead": {
"id": 2,
"type": "astronaut",
"code": "8685543559",
"name": "Brandon Rodriguez",
"role": "engineer"
}
},
"rocket": {
"id": 16,
"code": "4150234806"
},
"station": {
"code": "917844342",
"name": "Lake Jeff",
"coordinates": "('33.76446', '-117.79394')"
},
"launch_date": "2024-10-14"
}
]
Advanced features
Custom endpoints
The project demonstrates how to create custom endpoints for specific operations. For example, the crew resource includes a custom endpoint for assigning leads:
@route.post()
async def assign_lead(self, astronaut: Key[Astronaut]) -> Self:
lead = await astronaut.resolve()
if lead not in self.astronauts:
raise ValueError('The lead must be part of the crew')
self.lead = lead
return self
This endpoint ensures that only crew members can be assigned as leads.
Resource relationships
The project showcases also various relationship types:
class Rocket(CRUDResource):
...
parts: list['RocketPart'] = Field(default_factory=list) # (1)!
class Crew(CRUDResource):
...
astronauts: list[Astronaut] = Field( # (2)!
default_factory=list,
rel_load='selectin',
)
class Crew(CRUDResource):
...
lead: Astronaut | None = Field( # (3)!
default=None,
init=False,
association_alias='crew_lead',
)
- The
Rocket
resource has a one-to-many relationship withRocketPart
resources. - The
Crew
resource has a many-to-many relationship withAstronaut
resources. - The
Crew
resource has a one-to-one relationship with anAstronaut
resource representing the crew lead.