Fpy is a user-friendly spacecraft scripting language for the F Prime flight software framework.
Principles
Fpy has a few principles:
- Be safe
- Be pragmatic
- Be a joy to work with
The art of making a good language is to restrict the user in a good way
-- Andrey Breslav, creator of Kotlin
Overview
This repository contains the Fpy compiler, which emits Fpy bytecode. The Fpy bytecode runs on the FpySequencer virtual machine. If you're interested in contributing, see the Developer's Guide.
User's Guide
This guide is a quick overview of the most important features of Fpy. It should be easy to follow for someone who has used Python and F Prime before.
Compiling and Running a Sequence
First, make sure fprime-fpy is installed:
$ pip install fprime-fpy
Fpy sequences are suffixed with .fpy. Let's make a test sequence that dispatches a no-op:
# hash denotes a comment
# assume this file is named "test.fpy"
# use the full name of the no-op command:
CdhCore.cmdDisp.CMD_NO_OP() # empty parentheses indicate no arguments
You can compile it with fprime-fpyc test.fpy --dictionary Ref/build-artifacts/Linux/dict/RefTopologyDictionary.json
Make sure your deployment topology has an instance of the Svc.FpySequencer component. You can run the sequence by passing it in as an argument to the Svc.FpySequencer.RUN command.
Logging
Fpy supports logging F Prime events:
log("hello world!") # creates an ACTIVITY_HI event with the text "hello world!"
You can configure the severity of the event:
log("uh oh", Fw.LogSeverity.WARNING_HI)
log("oh no!", Fw.LogSeverity.FATAL)
All F Prime severity levels are supported.
At the moment, only constant string arguments are supported. See Strings.
Commands
Fpy supports calling any command in the F Prime dictionary:
CdhCore.cmdDisp.CMD_NO_OP()
# no delay between commands
CdhCore.cmdDisp.CMD_NO_OP_STRING("hello world!")
# the sequence waits until a command response is returned
Commands arguments are type checked, and they do not need to be constants. You can pass command arguments by name:
CdhCore.cmdDisp.CMD_NO_OP_STRING(arg1="hello world!")
If a command fails and the response isn't handled, the sequence will exit with an error:
CdhCore.exampleComponent.CMD_THAT_WILL_FAIL()
# sequence exits with an error
You can suppress errors by handling the return value of the command:
success: Fw.CmdResponse = CdhCore.exampleComponent.CMD_THAT_WILL_FAIL()
# cmd response is handled, sequence proceeds normally
if success == Fw.CmdResponse.EXECUTION_ERROR:
log("Command failed!")
You can configure whether unhandled command failures cause the sequence to exit by setting the flags.assert_cmd_success Boolean flag:
flags.assert_cmd_success = False
CdhCore.exampleComponent.CMD_THAT_WILL_FAIL()
# sequence proceeds normally
flags.assert_cmd_success is kind of like Bash's set -e and set +e commands. The flag defaults to True.
Variables and Basic Types
Fpy supports statically-typed, mutable local variables. You can change their value, but the type of the variable can't change.
This is how you declare a variable, and change its value:
unsigned_var: U8 = 0
# this is a variable named unsigned_var with a type of unsigned 8-bit integer and a value of 0
unsigned_var = 123
# now it has a value of 123
For types, Fpy has most of the same basic ones that FPP does:
- Signed integers:
I8, I16, I32, I64 - Unsigned integers:
U8, U16, U32, U64 - Floats:
F32, F64 - Boolean:
bool - Time:
Fw.Time
Float literals can include either a decimal point or exponent notation (5.0, .1, 1e-5), and Boolean literals have a capitalized first letter: True, False. There is no way to differentiate between signed and unsigned integer literals.
Note there is currently no built-in string type. See Strings.
Type coercion and casting
If you have a lower-bitwidth numerical type and want to turn it into a higher-bitwidth type, this happens automatically:
low_bitwidth_int: U8 = 123
high_bitwidth_int: U32 = low_bitwidth_int
# high_bitwidth_int == 123
low_bitwidth_float: F32 = 123.0
high_bitwidth_float: F64 = low_bitwidth_float
# high_bitwidth_float == 123.0
However, the opposite produces a compile error:
high_bitwidth: U32 = 25565
low_bitwidth: U8 = high_bitwidth # compile error
If you are sure you want to do this, you can manually cast the type to the lower-bitwidth type:
high_bitwidth: U32 = 16383
low_bitwidth: U8 = U8(high_bitwidth) # no more error!
# low_bitwidth == 255
This is called downcasting. It has the following behavior:
- 64-bit floats are downcasted to 32-bit floats as if by
static_cast<F32>(f64_value)in C++ - Unsigned integers are bitwise truncated to the desired length
- Signed integers are first reinterpreted bitwise as unsigned, then truncated to the desired length. Then, if the sign bit of the resulting number is set,
2 ** dest_type_bitsis subtracted from the resulting number to make it negative. This may have unintended behavior so use it cautiously.
You can turn an int into a float implicitly:
int_value: U8 = 123
float_value: F32 = int_value
But the opposite produces a compile error:
float_value: F32 = 123.0
int_value: U8 = float_value # compile error
Instead, you have to manually cast:
float_value: F32 = 123.0
int_value: U8 = U8(float_value)
# int_value == 123
In addition, you have to cast between signed/unsigned ints:
uint: U32 = 123123
int: I32 = uint # compile error
int: I32 = I32(uint)
# int == 123123
Dictionary Types
Fpy also has access to all structs, arrays and enums in the F Prime dictionary:
# you can access enum constants by name:
enum_var: Fw.Success = Fw.Success.SUCCESS
# you can construct arrays:
array_var: Ref.DpDemo.U32Array = [0, 1, 2, 3, 4]
# you can construct structs:
struct_var: Fw.TimeInterval = {seconds: 0, useconds: 1000}
If a struct or array has a default value for a member/element, it will use that default value if you don't provide one. The default timeBase of an Fw.TimeValue is overridden to TimeBase.TB_WORKSTATION_TIME. You can configure this by passing the --time-base flag.
Trailing commas are allowed in these expressions.
Math
You can do basic math and store the result in variables in Fpy:
pemdas: F32 = 1 - 2 + 3 * 4 + 10 / 5 * 2 # == 15.0
Fpy supports the following math operations:
- Basic arithmetic:
+, -, *, / - Modulo:
% - Exponentiation:
** - Floor division:
// - Natural logarithm:
log(F64) - Absolute value:
fabs(F64), iabs(I64)
The behavior of these operators is designed to mimic Python.
Note that division always returns a float. This means that
5 / 2 == 2.5, not2. This may be confusing coming from C++, but it is consistent with Python. If you want integer division, use the//operator.
Getting Telemetry Channels and Parameters
Fpy supports getting the value of telemetry channels:
cmds_dispatched: U32 = CdhCore.cmdDisp.CommandsDispatched
signal_pair: Ref.SignalPair = Ref.SG1.PairOutput
It's important to note that if your component hasn't written telemetry to the telemetry database (TlmPacketizer or TlmChan) in a while, the value the sequence sees may be old. Make sure to regularly write your telemetry!
Fpy supports getting the value of parameters:
prm_3: U8 = Ref.sendBuffComp.parameter3
A significant limitation of this is that it will only return the value most recently saved to the parameter database. This means you must command _PRM_SAVE before the sequence will see the new value.
Note: If a telemetry channel and parameter have the same fully-qualified name, the fully-qualified name will get the value of the telemetry channel
Conditionals
Fpy supports comparison operators:
value: bool = 1 > 2 and (3 + 4) != 5
- Inequalities:
>, <, >=, <= - Equalities:
==, != - Boolean functions:
and, or, not
Boolean and and or short-circuit just like Python: the right-hand expression only evaluates when the result is still undecided.
The inequality operators can compare two numbers of any type together. The equality operators, in addition to comparing numbers, can check for equality between two values of the same type:
record1: Svc.DpRecord = Svc.DpRecord(0, 1, 2, 3, 4, 5, Fw.DpState.UNTRANSMITTED)
record2: Svc.DpRecord = Svc.DpRecord(0, 1, 2, 3, 4, 5, Fw.DpState.UNTRANSMITTED)
records_equal: bool = record1 == record2 # == True
If/elif/else
You can branch off of conditionals with if, elif and else:
random_value: I8 = 4 # chosen by fair dice roll. guaranteed to be random
if random_value < 0:
log("won't happen")
elif random_value > 0 and random_value <= 6:
log("should happen!")
else:
log("uh oh...")
This is particularly useful for checking telemetry channel values:
# dispatch a no-op
CdhCore.cmdDisp.CMD_NO_OP()
# the commands dispatched count should be >= 1
if CdhCore.cmdDisp.CommandsDispatched >= 1:
log("should happen")
Check statement
A check statement is like an if, but its condition has to hold true (or "persist") for some amount of time.
Every check must have a timeout clause saying how long it is willing to wait. Use timeout never to wait forever:
check CdhCore.cmdDisp.CommandsDispatched > 30 timeout never persist {seconds: 15}:
log("more than 30 commands for 15 seconds!")
If you don't specify a value for persist, the condition only has to be true once.
Instead of never, you can give timeout a Fw.TimeInterval duration, measured from when the check is entered, after which the check gives up:
check CdhCore.cmdDisp.CommandsDispatched > 30 timeout {seconds: 60} persist {seconds: 2}:
log("more than 30 commands for 2 seconds!")
You can also specify a timeout: body, which executes if the check times out:
check CdhCore.cmdDisp.CommandsDispatched > 30 timeout {seconds: 60} persist {seconds: 2}:
log("more than 30 commands for 2 seconds!")
timeout:
log("took more than 60 seconds :(")
Finally, you can specify a period at which the condition should be checked:
check CdhCore.cmdDisp.CommandsDispatched > 30 timeout never period {seconds: 1}: # check every 1 second
log("more than 30 commands!")
If you don't specify a value for period, the default period is 0 seconds, so the condition is checked on every checkTimers call.
The timeout, persist and period clauses can appear in any order. They can also be spread across multiple lines:
check CdhCore.cmdDisp.CommandsDispatched > 30
timeout {seconds: 60}
persist {seconds: 2}
period {seconds: 1}
log("more than 30 commands for 2 seconds!")
timeout:
log("took more than 60 seconds :(")
If you just want to wait until a condition is true without running any body, you can omit the colon and body:
check CdhCore.cmdDisp.CommandsDispatched > 30 timeout {seconds: 60}
# execution continues here once the condition is satisfied (or times out)
log("done waiting!")
Getting Struct Members and Array Items
You can access members of structs by name, or array elements by index:
# access struct members with "." syntax
signal_pair_time: F32 = Ref.SG1.PairOutput.time
# access array elements with "[]" syntax
com_queue_depth_0: U32 = ComCcsds.comQueue.comQueueDepth[0]
You can also reassign struct members or array elements:
# Ref.SignalPair is a struct type
signal_pair: Ref.SignalPair = Ref.SG1.PairOutput
signal_pair.time = 0.2
# Svc.ComQueueDepth is an array type
com_queue_depth: Svc.ComQueueDepth = ComCcsds.comQueue.comQueueDepth
com_queue_depth[0] = 1
For and while loops
You can loop while a condition is true:
counter: U64 = 0
while counter < 100:
counter = counter + 1
# counter == 100
Keep in mind, a busy-loop will eat up the whole thread of the Svc.FpySequencer component. If you do this for long enough, the queue will fill up and the component will assert. You may want to include at least one sleep in such a loop:
while True:
# this will execute one loop body every time checkTimers is called
sleep()
You can also loop over a range of integers:
sum: I64 = 0
# loop i from 0 inclusive to 5 exclusive
for i in 0..5:
sum = sum + i
# sum == 10
The loop variable, in this case i, is always of type I64. If a variable with the same name as the loop variable already exists, it can be reused as long as it is an I64:
i: I64 = 123
for i in 0..5: # okay: reuse of `i`
sum = sum + i
There is currently no support for a step size other than 1.
While inside of a loop, you can break out of the loop:
counter: U64 = 0
while True:
counter = counter + 1
if counter == 100:
break
# counter == 100
You can also continue on to the next iteration of the loop, skipping the remainder of the loop body:
odd_numbers_sum: I64 = 0
for i in 0..10:
if i % 2 == 0:
continue
odd_numbers_sum = odd_numbers_sum + i
# odd_numbers_sum == 25
Functions
You can define and call functions:
def foobar():
if 1 + 2 == 3:
log("foo")
foobar()
Functions can have arguments and return types:
def add_vals(a: U64, b: U64) -> U64:
return a + b
assert add_vals(1, 2) == 3
Function arguments are passed by value. Trailing commas are allowed in the argument list.
Functions can have default argument values:
def greet(times: I64 = 3):
for i in 0..times:
log("hello")
greet() # uses default: prints 3 times
greet(1) # prints once
Default values must be constant expressions (literals, enum constants, type constructors with const args, etc.). You can't use telemetry, variables, or function calls as defaults.
Functions can access top-level variables:
counter: I64 = 0
def increment():
counter = counter + 1
increment()
increment()
assert counter == 2
Functions can call each other or themselves:
def recurse(limit: U64):
if limit == 0:
return
log("tick")
recurse(limit - 1)
recurse(5) # prints "tick" 5 times
Functions can only be defined at the top level, so not inside loops, conditionals, or other functions.
Sequence arguments
You can define arguments for a sequence similarly to function arguments:
# in "example.fpy"
# sequence() must be the first statement in the file
sequence(foo: U32, bar: bool)
if foo == 123 and bar:
log("foobar!")
Sequence arguments cannot have default values. They are always passed by value.
There are two ways of calling sequences, each of which supports passing sequence arguments: from another sequence, or from the ground.
When you call a sequence from another sequence, you can provide argument values in the command:
# call the example.bin sequence, in blocking mode, with the argument values 123 and True
# passing args by name is supported
Ref.seqDisp.RUN_ARGS("example.bin", Svc.BlockState.BLOCK, 123, bar=True)
To call a sequence from the ground, use the fprime-fpy-cmd CLI:
# this does the same thing as the previous example
$ fprime-fpy-cmd 'Ref.seqDisp.RUN_ARGS("example.bin", Svc.BlockState.BLOCK, 123, bar=True)' -d TopologyDictionary.json
To use this, you must have a running GDS. See fprime-fpy-cmd for more info.
The compiler checks the provided argument names and types against those declared in the sequence file you're calling. To do this, it must find a corresponding .fpy file for the .bin file you passed to the RUN_ARGS command. The --seq-map BIN_PREFIX=FPY_PREFIX argument controls how it searches for the .fpy file. For each --seq-map arg passed to the CLI, if the .bin path has a prefix matching BIN_PREFIX, it replaces that prefix with FPY_PREFIX and the suffix with .fpy, and if the file exists, it checks that file for the sequence argument types and names. An empty BIN_PREFIX matches every path.
Relative and Absolute Sleep
You can pause the execution of a sequence for a relative duration, or until an absolute time:
log("second 0")
# sleep for 1 second
sleep(1)
log("second 1")
# sleep for half a second
sleep(useconds=500_000)
# sleep until the next checkTimers call on the Svc.FpySequencer component
sleep()
log("checkTimers called!")
log("today")
# sleep until 1234567890 seconds and 0 microseconds after the epoch
sleep_until({seconds: 1234567890, useconds: 0})
log("much later")
You can also use the time() function to parse ISO 8601 timestamps:
# Parse an ISO 8601 timestamp (UTC with Z suffix)
sleep_until(time("2025-12-19T14:30:00Z"))
# With microseconds
t: Fw.Time = time("2025-12-19T14:30:00.123456Z")
sleep_until(t)
# Customize timeBase and timeContext (defaults are TimeBase.TB_WORKSTATION_TIME and 0)
t: Fw.Time = time("2025-12-19T14:30:00Z", timeBase=TimeBase.TB_SC_TIME, timeContext=1)
Make sure that the Svc.FpySequencer.checkTimers port is connected to a rate group. The sequencer only checks if a sleep is done when the port is called, so the more frequently you call it, the more accurate the wakeup time.
Working with Time
Fpy provides built-in functions and operators for working with Fw.Time and Fw.TimeInterval types (aliases for Fw.TimeValue and Fw.TimeIntervalValue respectively).
You can get the current time with now():
current_time: Fw.Time = now()
The underlying implementation of now() just calls the getTime port on the FpySequencer component.
You can compare two Fw.Time values with comparison operators:
t1: Fw.Time = now()
sleep(seconds=1)
t2: Fw.Time = now()
assert t1 <= t2
If the times are incomparable due to having different time bases, the sequence will assert. To safely compare times which may have different time bases, use the time_cmp function, in time.fpy.
You can also compare two Fw.TimeInterval values:
interval1: Fw.TimeInterval = {seconds: 5}
interval2: Fw.TimeInterval = {seconds: 10}
assert interval1 < interval2
You can add a Fw.TimeInterval to a Fw.Time:
current: Fw.Time = {timeBase: TimeBase.TB_PROC_TIME, timeContext: 0, seconds: 100, useconds: 500000}
offset: Fw.TimeInterval = {seconds: 60}
assert (current + offset).seconds == 160
You can subtract two Fw.Time values to get a Fw.TimeInterval:
start: Fw.Time = {timeBase: TimeBase.TB_PROC_TIME, timeContext: 0, seconds: 100, useconds: 0}
end: Fw.Time = {timeBase: TimeBase.TB_PROC_TIME, timeContext: 0, seconds: 105, useconds: 500000}
assert (end - start).seconds == 5
Subtraction of two Fw.Time values asserts that both times have the same time base and that the first argument is greater than or equal to the second. If these conditions are not met, the sequence will exit with an error.
If at any point the output value would overflow, the sequence will exit with an error. Under the hood, these operators are just calling the built in
time_cmp,time_sub,time_add, etc. functions intime.fpy.
Exit Macro
You can end the execution of the sequence early by calling the exit macro:
# exit takes a U8 argument
# 0 is the error code meaning "no error"
exit(0)
# anything else means an error occurred, and will show up in telemetry
exit(123)
Assertions
You can assert that a Boolean condition is true:
# won't end the sequence
assert 1 > 0
# will end the sequence
assert 0 > 1
You can also specify an error code to be raised if the expression is not true:
# will raise an error code of 123
assert 1 > 2, 123
Imports
You can import sequences, but the sequence you're importing must only contain functions or other imports:
# helper_lib.fpy
def add_two(a: U64, b: U64) -> U64:
return a + b
# underscore prefix denotes a library-internal function, warns if imported
def _plus_one(a: U64) -> U64:
return a + 1
# main_seq.fpy
import helper_lib
assert helper_lib.add_two(1, 2) == 3
You can optionally add an alias:
import helper_lib as aliased
assert aliased.add_two(1, 2) == 3
Alternatively, you can pick specific functions to import:
from helper_lib import (
add_two as helper_add_two, # optional `as` alias
_plus_one # <-- this warns because it starts with an underscore
)
assert helper_add_two(1, 2) == 3
assert _plus_one(1) == 2
Or you can import all functions at once:
# this doesn't import functions whose names begin with an underscore
from helper_lib import *
assert add_two(1, 2) == 3
You can specify paths to search for imports with the -i/--imports argument, which can be passed more than once.
If you want to specify a path relative to the file you're writing the import statement in, you can prefix the import path with a dot:
# search for helper lib in the script's directory
from .helper_lib import add_two
Each dot you add goes up a directory level before beginning the search:
from ...parent.dir.helper_lib import add_two
Writing to arbitrary ports
Fpy has a powerful feature which allows it to write arbitrary data to an array of ports on the sequencer.
value: U32 = 42
write_to_port(Svc.Fpy.SerialPortIndex.EXAMPLE_PORT_0, value)
This will come out of the sequencer's serialOut port on the port with index EXAMPLE_PORT_0. The Svc.Fpy.SerialPortIndex enum is configurable, so you can change the names and numbers of the ports to correspond to actual functions.
Because there is no type checking on the value you pass in to the ports, it is recommended that you wrap the port in a function:
def fdir_error_count(count: U32):
write_to_port(Svc.Fpy.SerialPortIndex.FDIR_0, count)
... and then import this function wherever you need it.
Strings
Fpy does not support a fully-fledged string type yet. You can pass a string literal as an argument to a command or builtin, but you cannot pass a string from a telemetry channel. You also cannot store a string in a variable, or perform any string manipulation, or use any types anywhere which have strings as members or elements. This is due to F Prime strings having a dynamic serialized size. These features will be added in a later Fpy update.
Developer's Guide
Setup and workflow
This project uses uv to manage its environment.
uv sync(creates.venvand installs all dependencies, including dev tools)uv run pre-commit install(one-time: installs the git pre-commit hooks)- Make changes to the source
- Run the test suite. See Running tests (some tests have additional setup requirements).
Pre-commit hooks
The hooks are defined in .pre-commit-config.yaml and run automatically on git commit once installed:
- black formats the staged Python files.
You can run all hooks against the whole repo at any time with uv run pre-commit run --all-files.
Tools
fprime-fpyc
Some useful compiler flags are:
--emit {fpybin,fpyasm,llvm-ir,wasm,wat}: output format. Defaults tofpybin(binary fpy bytecode);fpyasmemits human-readable bytecode assembly, andllvm-ir/wasm/watemit the LLVM/WebAssembly backend outputs.--ignore/--error: comma-separated warning types (orall) to silence or promote to hard errors.--debug: print a stack trace of where each compile error is generated.
fprime-fpy-cmd
Compiles a single line of Fpy source (one command with constant arguments) and uplinks it to a running GDS, e.g. fprime-fpy-cmd 'Ref.seqDisp.RUN_ARGS("seq.bin", NO_WAIT)' -d dict.json. Uplinks over ZMQ by default; pass --tcp-addr host:port to use TCP instead. A RUN_ARGS line that passes sequence arguments needs --seq-map to locate the called sequence's .fpy source.
fprime-fpy-asm
fprime-fpy-asm assembles human-readable .fpybc bytecode files into binary .bin files.
fprime-fpy-disasm
fprime-fpy-disasm disassembles binary .bin files into human-readable .fpybc bytecode.
Running tests
Use pytest to run the test suite:
pytest
Some tests compile and run sequences. Those tests will generate both Fpy bytecode and WASM bytecode. The resulting binaries are run on a harness wrapping the appropriate sequencer component (Svc/FpySequencer for Fpy bytecode, and Svc/WasmSequencer for WASM bytecode).
To run these tests, the following steps are required:
- The submodules must be checked out:
git submodule update --init test/fprime test/fprime-wasm - The harness build tools (cmake, ninja, fprime-util, fpp) and the
wasmextra.uv syncinstalls them as part of the dev environment; with pip the extra ispip install -e '.[wasm]'. - A Rust toolchain (the wasm sequencer builds the spacewasm interpreter with cargo). Install via rustup.
Each harness is built automatically when the first test that needs it runs. The wasm harness build applies a small local patch to the submodule first (see test/harness/patches/README.md).
--backend
--backend fpybc or --backend wasm restricts the run to one backend (the default is both):
pytest --backend fpybc
A few behaviors are deliberately backend-specific; those tests carry the fpybc_only/wasm_only markers (and skip when their backend is not selected). Tests whose expected values differ by backend run once per selected backend via the single_backend fixture. Tests marked @pytest.mark.wasm exercise the LLVM/wasm toolchain itself and run except under --backend fpybc.
--use-gds
Passing --use-gds runs sequences against a live F Prime GDS deployment, see Running on a test F Prime deployment for how to set one up and the full command line (a --dictionary argument is also required).
Running on a test F Prime deployment
git clone git@github.com:zimri-leisher/fprime-fpy-testbedcd fprime-fpy-testbedgit submodule update --init --recursive- Make a venv, install fprime requirements
cd Reffprime-util generate -ffprime-util build -j16fprime-gds. You should see a green circle in the top right.- In the
fpyrepo,pytest --use-gds --dictionary test/fpy/RefTopologyDictionary.jsonwill run all of the test sequences against the live GDS deployment. It will take several minutes.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fprime_fpy-0.6.0.tar.gz.
File metadata
- Download URL: fprime_fpy-0.6.0.tar.gz
- Upload date:
- Size: 497.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
186355e78c0b154a97c6a6e00b9581cf5779eaa864c59c9a7221611ab15c654f
|
|
| MD5 |
547b3e02cd092d9b6c7dc7f687dc3d02
|
|
| BLAKE2b-256 |
ba17c5a2500577a07d16a40c7259b233d8139976e5fdf2273e99b155164be99a
|
Provenance
The following attestation bundles were made for fprime_fpy-0.6.0.tar.gz:
Publisher:
publish.yml on fprime-community/fpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fprime_fpy-0.6.0.tar.gz -
Subject digest:
186355e78c0b154a97c6a6e00b9581cf5779eaa864c59c9a7221611ab15c654f - Sigstore transparency entry: 2712406083
- Sigstore integration time:
-
Permalink:
fprime-community/fpy@8efcaf986ec126f24edf7665c133e9f4523a7e2d -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/fprime-community
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8efcaf986ec126f24edf7665c133e9f4523a7e2d -
Trigger Event:
release
-
Statement type:
File details
Details for the file fprime_fpy-0.6.0-py3-none-any.whl.
File metadata
- Download URL: fprime_fpy-0.6.0-py3-none-any.whl
- Upload date:
- Size: 191.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2296af7bb5299c40a30af40b29e8de2b027dab90960db91ee30ade1962a5ff87
|
|
| MD5 |
d6e5d309b3a748f628a41ba3828d985f
|
|
| BLAKE2b-256 |
79b9176d45e0d8fa3814efbce664faafccdd0b7c2a37286c784cebc15b146275
|
Provenance
The following attestation bundles were made for fprime_fpy-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on fprime-community/fpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fprime_fpy-0.6.0-py3-none-any.whl -
Subject digest:
2296af7bb5299c40a30af40b29e8de2b027dab90960db91ee30ade1962a5ff87 - Sigstore transparency entry: 2712406720
- Sigstore integration time:
-
Permalink:
fprime-community/fpy@8efcaf986ec126f24edf7665c133e9f4523a7e2d -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/fprime-community
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8efcaf986ec126f24edf7665c133e9f4523a7e2d -
Trigger Event:
release
-
Statement type: