mirror of
https://github.com/status-im/status-python-sdk.git
synced 2026-08-27 20:21:07 +00:00
PyPI: PIP setup
- Rename repository from `bot` to `status_sdk` - Add `pyproject.toml` file - Update code documentation - Move `docker-compose.yaml` in folder `status_sdk` - Update `Account` docker volume paths - Update Agent example - Add `volume_folder` to `Account`
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
|
||||

|
||||
|
||||
[Status](http://status.app/) is a decentralized, open-source super app combining a crypto wallet, messenger, and community spaces. It uses peer-to-peer technology so no central server can censor your messages or access your data.
|
||||
|
||||
The initial Python Status Backend was built with testing in mind, instead of easy developer access. The objective of this repository is to make a SDK that is:
|
||||
|
||||
- **light** - as less external packages when it comes to working with Status App
|
||||
- **fast** - quick to get started with Status Python
|
||||
- **documented** - clear explanations of what was done and **why it was done in a specific way**.
|
||||
|
||||
Currently this repository is not on [PyPi](https://pypi.org/) but will be added when core functionality has been devleoped and tested.
|
||||
|
||||
## How it works
|
||||
|
||||
```mermaid
|
||||
@@ -25,7 +25,7 @@ graph TB
|
||||
end
|
||||
|
||||
|
||||
subgraph bot[Python SDK]
|
||||
subgraph bot[status-im/status-python-sdk]
|
||||
REQUIREMENTS[requirements.txt]
|
||||
SDK[class Account]
|
||||
SIGNAL[class Signal]
|
||||
@@ -56,7 +56,7 @@ To access Python funcitonality you will have to set up [Status Backend](https://
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant Docker
|
||||
participant Python@{"alias": "status-im/status-bot"}
|
||||
participant Python@{"alias": "status-im/status-python-sdk"}
|
||||
participant Github@{"alias": "status-im/status-go" }
|
||||
|
||||
User ->> Docker: docker-compose up
|
||||
@@ -64,37 +64,72 @@ sequenceDiagram
|
||||
Docker ->> Docker: Build
|
||||
User ->> Docker: Run container
|
||||
User ->> Python: initialize module
|
||||
Note over User,Python: from bot import Account<br>account = Account()
|
||||
Note over User,Python: from status_sdk import Account<br>account = Account()
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
1. Setup environment. [Conda](https://www.anaconda.com/) example:
|
||||
```bash
|
||||
conda create -n status-sdk python=3.12
|
||||
#### Install
|
||||
|
||||
Clone the repository and move into it:
|
||||
|
||||
```
|
||||
git clone https://github.com/status-im/status-python-sdk.git
|
||||
cd status-python-sdk
|
||||
```
|
||||
|
||||
**Note**: Code has been tested with **Python 3.12**.
|
||||
Install the base library:
|
||||
|
||||
2. Install requirements
|
||||
|
||||
```bash
|
||||
pip install -r ./requirements.txt
|
||||
```
|
||||
pip install .
|
||||
```
|
||||
|
||||
If you want to modify the library itself without having to reinstall:
|
||||
|
||||
```
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
#### Uninstall
|
||||
|
||||
```
|
||||
pip uninstall status-sdk
|
||||
```
|
||||
|
||||
|
||||
### Docker
|
||||
|
||||
Setup [`status-im/status-go`](https://github.com/status-im/status-go/) with the provided `docker-compose.yaml` file.
|
||||
[`status-im/status-go`](https://github.com/status-im/status-go/) runs from the provided [`docker-compose.yaml`](./status_sdk/docker-compose.yaml) file. It does not use a pre-built image - it builds the backend from source, pulling directly from GitHub.
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
If you would like to initialize and start the container with Python:
|
||||
To run on Windows, please make sure you have set up [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). It is **required** for the `build: context` above. The SDK invokes Docker through WSL so it can build the Linux image and clone the repository during the build. If the `build` is changed to point to a local repository, then WSL is not required.
|
||||
|
||||
You can set it up in **two** ways.
|
||||
|
||||
#### With Python
|
||||
|
||||
Use [`launch_docker_container`](./docs/utils.md#launch_docker_containercommitnone-wait_seconds5-platformlinuxamd64), which builds and starts the container for you. This is the recommended option, as it handles platform selection and (on Windows) recovers from stale Docker mounts:
|
||||
|
||||
```python
|
||||
from bot import launch_docker_container
|
||||
from status_sdk import launch_docker_container
|
||||
launch_docker_container()
|
||||
```
|
||||
|
||||
**Note**: To run on Windows, please make sure you have set up [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
#### Manually
|
||||
|
||||
Run the compose file yourself. It lives inside the installed package, so point Docker at it:
|
||||
|
||||
```
|
||||
docker compose -f status_sdk/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
The compose file reads two variables from the environment. Both have a default, so the command above works as-is, but they can be overridden:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|-----|-----|-------------|
|
||||
| `STATUS_GO_REF` | `develop` | The [`status-im/status-go`](https://github.com/status-im/status-go/) git ref (commit SHA, branch or tag) to build from. |
|
||||
| `STATUS_GO_PLATFORM` | `linux/amd64` | The platform the image is built for. |
|
||||
|
||||
```
|
||||
STATUS_GO_REF=2bee8b6a38cdc8f92d74e2dbb8c4e77fbbeea149 STATUS_GO_PLATFORM=linux/amd64 docker compose -f status_sdk/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import shutil, os, subprocess, sys, time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from .logger import Logger
|
||||
from . import exceptions
|
||||
|
||||
def launch_docker_container(commit: Optional[str] = None, wait_seconds: int = 5):
|
||||
"""
|
||||
Launch the Status Backend Docker container using `docker-compose.yaml`
|
||||
|
||||
Parameters:
|
||||
- `commit` - the commit SHA. If no commit is provided, the latest version is pulled
|
||||
- `wait_seconds` - nunmber of seconds to wait before the code resumes. Sleep prevents calling `class Account` faster than launching the docker container. This only happens when the container already exists and it is must be turned on.
|
||||
"""
|
||||
logger = Logger()
|
||||
platform = sys.platform
|
||||
is_windows = platform == "win32"
|
||||
if not shutil.which("docker"):
|
||||
raise exceptions.DockerError("Please install Docker.")
|
||||
|
||||
logger.info(f"Running Docker on {platform}")
|
||||
ref = commit if commit else "develop"
|
||||
DOCKER_COMPOSE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "docker-compose.yaml")
|
||||
docker_path = DOCKER_COMPOSE_PATH
|
||||
if is_windows:
|
||||
p = Path(DOCKER_COMPOSE_PATH)
|
||||
drive = p.drive.rstrip(":").lower()
|
||||
docker_path = f"/mnt/{drive}/" + "/".join(p.parts[1:])
|
||||
|
||||
cmd = ["env", f"STATUS_GO_REF={ref}", "docker", "compose", "-f", docker_path, "up", "-d", "--build"]
|
||||
|
||||
if is_windows:
|
||||
if not shutil.which("wsl"):
|
||||
raise exceptions.DockerError("Please install wsl - https://learn.microsoft.com/en-us/windows/wsl/install.")
|
||||
cmd.insert(0, "wsl")
|
||||
|
||||
logger.info(f"Running:\n{' '.join(cmd)}")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=os.path.dirname(DOCKER_COMPOSE_PATH),
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise exceptions.DockerError(result.stderr.strip())
|
||||
|
||||
logger.info(f"Sleeping for {wait_seconds}s")
|
||||
time.sleep(wait_seconds)
|
||||
+60
-49
@@ -84,7 +84,7 @@ Wallet features are optional and can be omitted if not required for your use cas
|
||||
|
||||

|
||||
|
||||
## `Account(domain="localhost", backend_port=8080, media_port=9000, is_secure=False, backup_folder=None)`
|
||||
## `Account(domain="localhost", backend_port=8080, media_port=9000, is_secure=False, backup_folder=None, volume_folder=None)`
|
||||
|
||||
Create a new `Account` instance ready to be logged in. The constructor wires the SDK to a running [Status Backend](https://github.com/status-im/status-go) at the given `domain` and `backend_port`, prepares the local `assets/` folder (used for image uploads, such as the [profile picture](./account.md#profile_picture)) and `backups/` folder (used for [backup uploads](./account.md#backups) and recovery).
|
||||
|
||||
@@ -94,12 +94,13 @@ Create a new `Account` instance ready to be logged in. The constructor wires the
|
||||
| `backend_port` | `int` | No | Port exposed by Status Backend. Defaults to `8080`. If this is changed, the published port for `backend_port` must be updated to match in `docker-compose.yaml` as well. |
|
||||
| `media_port` | `int` | No | Port exposed by the Status media server, used to fetch localhost images such as the [profile picture](./account.md#profile_picture). Defaults to `9000`. If this is changed, the published port for `media_port` must be updated to match in `docker-compose.yaml` as well. |
|
||||
| `is_secure` | `bool` | No | When `True`, the SDK communicates over `https`; otherwise `http` is used. Defaults to `False`. |
|
||||
| `backup_folder` | `str` | No | Absolute path on the host machine where `.bkp` files will be stored and loaded from. If not provided, the SDK's own `backups/` folder is used. See [Backups](./account.md#backups). |
|
||||
| `backup_folder` | `str` | No | Absolute path on the host machine where `.bkp` files will be created and loaded from. If not provided, the SDK's own `backups/` folder is used. See [Backups](./account.md#backups). |
|
||||
| `volume_folder` | `str` | No | Directory containing the `docker-compose.yaml` whose `backups/` and `assets/` folders are mounted into the Status Backend container. Defaults to this package's own installation folder (e.g. the `status_sdk` folder under `site-packages` when installed via `pip`). Set this when Status Backend is launched from a different `docker-compose.yaml`, such as a local clone of the repository. |
|
||||
|
||||
The constructor does not log into any account on its own - call [`login`](./account.md#loginpassword-key_uidnone-display_namenone-mnemonicnone-infura_tokennone-alchemy_tokennone-coingecko_api_keynone) afterwards. To discover what accounts already exist in the configured data directory, use the [`available_accounts`](./account.md#available_accounts) property, which is also populated automatically during initialization.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
```
|
||||
@@ -107,7 +108,7 @@ account = Account()
|
||||
Use a custom backup folder:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account(backup_folder=r"C:\\Users\\me\\status-backups")
|
||||
```
|
||||
@@ -115,7 +116,7 @@ account = Account(backup_folder=r"C:\\Users\\me\\status-backups")
|
||||
Connect to a Status Backend running on a different host or port:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account(
|
||||
domain="status-backend.internal",
|
||||
@@ -124,10 +125,20 @@ account = Account(
|
||||
)
|
||||
```
|
||||
|
||||
Run against a local clone of the repository instead of with [`launch_docker_container`](./utils.md#launch_docker_container):
|
||||
|
||||
```python
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account(volume_folder="/path/to/status-python-sdk/status_sdk")
|
||||
```
|
||||
|
||||
**Note**: Status Backend must be running before initializing `Account`. You can launch the backend container with [`launch_docker_container`](./utils.md#launch_docker_container). If the backend is not reachable on `domain:port`, calls to [`login`](./account.md#loginpassword-key_uidnone-display_namenone-mnemonicnone-infura_tokennone-alchemy_tokennone-coingecko_api_keynone) will fail.
|
||||
|
||||
**Note**: When `backup_folder` is set, [`backup`](./account.md#backup) moves the generated `.bkp` file out of the SDK's internal `backups/` folder into the provided path, and recovery via `mnemonic` will look in this same folder for `.bkp` files to auto-load. Make sure the folder exists and is writable.
|
||||
|
||||
**Note**: `volume_folder` must match the directory containing the `docker-compose.yaml` actually used to launch Status Backend, since that is what determines where Docker mounts `backups/` and `assets/` on the host. If `volume_folder` points elsewhere, `Account` will create and use folders that are never seen by the running container.
|
||||
|
||||
## Methods
|
||||
|
||||
### `login(password, key_uid=None, display_name=None, mnemonic=None, infura_token=None, alchemy_token=None, coingecko_api_key=None)`
|
||||
@@ -156,7 +167,7 @@ Returns the current `Account` instance, allowing method chaining.
|
||||
|
||||
#### Login with Display name
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -175,7 +186,7 @@ The code above is equivalent to the following screen on Status App:
|
||||
#### Login with ENS
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -194,7 +205,7 @@ You can purchase a **universal username** on Status App:
|
||||
#### Login with `key_uid`
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -207,7 +218,7 @@ account.login(**params)
|
||||
#### Recover account
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -227,7 +238,7 @@ The code above is equivalent to the following screen on Status App:
|
||||
#### Wallet setup
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -248,7 +259,7 @@ account.login(**params)
|
||||
Logout from the currently logged-in Status account. This method also clears the internal account state and stops the active messenger session. This function is also supported in `del` and when the script automatically finishes.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -274,7 +285,7 @@ Returns `str` representing the **Docker path** of the generated backup file. The
|
||||
The filename is generated by the Status Backend and follows the pattern `<suffix>_user_data.bkp`, where `<suffix>` is the **last 6 characters of the account's compressed public key**. For example, an account whose compressed key ends in `abc123` produces `abc123_user_data.bkp`. Because the suffix is derived deterministically from the account's key, the same account always maps to the same filename, which is how a backup is uniquely associated with its account.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account(backup_folder=r"C:\\Users\\me\\status-backups")
|
||||
params = {
|
||||
@@ -299,7 +310,7 @@ Send a text message to a specific chat. This method currently supports **text me
|
||||
| `message` | `str` | Yes | The text message to send. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -330,7 +341,7 @@ Messages can be fetched from:
|
||||
Returns `list[dict]` containing message objects. Timestamp fields returned by the backend are automatically converted into `datetime.datetime` objects.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
import datetime
|
||||
|
||||
account = Account()
|
||||
@@ -358,7 +369,7 @@ for message in messages:
|
||||
Listen for new incoming messages **in real time**. This method yields raw message events as they are received from the Status Backend [signal](./account.md#signallisten) `messages.new`. This method is ideal for developing real time chat applications
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
import datetime
|
||||
# For terminal readability only
|
||||
from rich import print as rprint
|
||||
@@ -400,7 +411,7 @@ Modes:
|
||||
Returns the current `Account` instance, allowing method chaining.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -443,7 +454,7 @@ Returns `bool`.
|
||||
| `False` | The contact does not exist or was already removed. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -473,7 +484,7 @@ Send a request to join a community using its invitation URL. The method parses t
|
||||
Returns `datetime.datetime` representing when the join request was submitted.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -506,7 +517,7 @@ Returns `pd.DataFrame`.
|
||||
| `source_id` | `str` | Source list from which the token was fetched. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -539,7 +550,7 @@ Returns `pd.DataFrame`.
|
||||
| `price` | `float` | Token price **for 1 `token_symbol`** in the given fiat currency (only present if `ccy` is provided). If you want to get the amount in the wallet, you must `amount * price`. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -566,7 +577,7 @@ data = account.get_balance(token_addresses)
|
||||
Access multuple chains:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -594,7 +605,7 @@ data = account.get_balance(token_addresses, chain_ids)
|
||||
Access multiple wallets:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -626,7 +637,7 @@ data = account.get_balance(token_addresses, chain_ids, wallets)
|
||||
Get token prices:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -705,7 +716,7 @@ Returns `pd.DataFrame`, sorted by `timestamp` in descending order (newest first)
|
||||
| `trx_fee` | `float` | Gas fee paid in the chain's native token, computed as `gas_price * gas_used / 10**18`. Populated only for `sent` rows; `0` for `received` rows since the receiver does not pay gas. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -744,7 +755,7 @@ Returns `str` representing the **transaction hash**. The hash can be appended to
|
||||
Send ETH:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -770,7 +781,7 @@ print(f"Transaction: https://etherscan.io/tx/{tx_hash}")
|
||||
Send an ERC-20 token by symbol:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -795,7 +806,7 @@ tx_hash = account.send_transaction(
|
||||
Send an ERC-20 token by contract address:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -821,7 +832,7 @@ tx_hash = account.send_transaction(
|
||||
Send on a different chain:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -873,7 +884,7 @@ Returns `str` representing the **transaction hash**. The hash can be appended to
|
||||
Swap **ETH** for an **ERC-20** token:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -897,7 +908,7 @@ print(f"Swap: https://etherscan.io/tx/{tx_hash}")
|
||||
Swap **ERC-20** for an **ETH** token:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -921,7 +932,7 @@ print(f"Swap: https://etherscan.io/tx/{tx_hash}")
|
||||
Swap **ERC-20** for an **ERC-20** token:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -965,7 +976,7 @@ Returns `list[dict]`, one entry per locally available account.
|
||||
| `created_at` | `datetime.datetime` | Timestamp when the account was created locally. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
# For terminal readability only
|
||||
from rich import print as rprint
|
||||
from rich.pretty import Pretty
|
||||
@@ -994,7 +1005,7 @@ Provides information about the currently logged-in account. If `login()` has not
|
||||
| `logged_in_timestamp` | `datetime.datetime` | Timestamp when the account successfully logged in. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1013,7 +1024,7 @@ Get or update the current display name of the logged‑in account.
|
||||
Returns `str` when reading the property.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1029,7 +1040,7 @@ print(account.display_name)
|
||||
You can update the display name by assigning a new value:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1052,7 +1063,7 @@ Get or update the **bio** of the currently logged‑in account. The length of th
|
||||
Returns `str` when reading the property.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1068,7 +1079,7 @@ print(account.bio)
|
||||
The value assigned to `bio` will automatically be converted to a string before being sent to the backend. You can update the bio by assigning a new value:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1085,7 +1096,7 @@ print(account.bio)
|
||||
You can also **clear the bio** by deleting the property:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1107,7 +1118,7 @@ Get or update the **profile picture** of the currently logged‑in account. The
|
||||
Returns `PIL.Image.Image` when reading the property, or `None` if no profile picture has been set.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1125,7 +1136,7 @@ if image:
|
||||
The file path assigned to `profile_picture` will be automatically set as the latest profile picture in Status App. If the given file does not exist or the extension is not supported, an **exception will be raised**. Supported image formats are `.jpg`, `.jpeg` and `.png`.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1154,7 +1165,7 @@ The property exposes the following methods:
|
||||
- `signal.expect()` - return a context manager that waits for one or more matching signals to arrive **after** you perform an action. This is the recommended way to make **async message calls**, since it removes the race conditions and infinite-loop risk of `get()`.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1191,7 +1202,7 @@ Default logger configuration:
|
||||
Example:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -1236,7 +1247,7 @@ Returns `dict[str, dict]` where the key is the contact's **public key**. This ma
|
||||
| `last_updated` | `datetime.datetime` | Timestamp when the contact information was last updated. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1304,7 +1315,7 @@ Channel permissions:
|
||||
| `token_gated` | `bool` | Whether the channel requires a token to participate. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1341,7 +1352,7 @@ Returns `pd.DataFrame`.
|
||||
| `status_alias` | `str` | Initial display name of the member when the account was created. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1370,7 +1381,7 @@ Returns `list[dict]` where each `dict` represents a chat that can be used with [
|
||||
| `name` | `str` | Either the display name of the user or the community channel name. |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
@@ -1400,7 +1411,7 @@ Returns `dict[int, str]`.
|
||||
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -1430,7 +1441,7 @@ Returns `pd.DataFrame`.
|
||||
| `symbol` | `str` | Token symbol (e.g. `ETH`, `USDT`). |
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
@@ -1448,7 +1459,7 @@ print(account.balance)
|
||||
You can convert the current balance into fiat currency by using a [ISO 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html) in the `[]` accessor:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
from status_sdk import Account
|
||||
|
||||
account = Account()
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
+65
-5
@@ -6,7 +6,7 @@ Helper functions for setting up the Status Backend environment.
|
||||
|
||||
## Methods
|
||||
|
||||
### `launch_docker_container(commit=None, wait_seconds=5)`
|
||||
### `launch_docker_container(commit=None, wait_seconds=5, platform="linux/amd64")`
|
||||
|
||||
Launch Status Backend Docker container in the background using `docker-compose.yaml`. If `docker` is not installed, or if the container fails to start, an **exception will be raised** with the error message from Docker. The container is built from [`status-im/status-go`](https://github.com/status-im/status-go) at the git ref you choose:
|
||||
|
||||
@@ -16,22 +16,82 @@ context: https://github.com/status-im/status-go.git#${STATUS_GO_REF:-develop}
|
||||
|
||||
The image is always rebuilt (`docker compose up --build`) so a newly chosen `commit` is picked up instead of reusing a previously built image.
|
||||
|
||||
**Note**: The container mounts the SDK's `backups/` and `assets/` folders as Docker volumes. Make sure the repository has **read and write permissions**, otherwise the container will fail to start or [backups](./account.md#backups) and [profile pictures](./account.md#profile_picture) will not be saved. On Docker Desktop the repository must also be a **shared path** (see [Windows](./utils.md#windows) and [Mac](./utils.md#mac)).
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|-----|-----|-----|-------------|
|
||||
| `commit` | `str` | No | The `status-im/status-go` git ref to build from - a commit SHA, branch, or tag. When omitted, the latest `develop` branch is built. |
|
||||
| `wait_seconds` | `int` | No | Number of seconds to pause after the `docker compose up` command returns, giving Status Backend enough time to finish booting before subsequent code runs. Defaults to `5`. This matters mainly when the container already exists and is being restarted, because `docker compose up` returns immediately while the backend is still warming up - instantiating [`Account`](./account.md#accountdomainlocalhost-port8080-is_securefalse-backup_foldernone) too quickly will fail to connect. |
|
||||
| `wait_seconds` | `int` | No | Number of seconds to pause after the `docker compose up` command returns, giving Status Backend enough time to finish booting before subsequent code runs. Defaults to `5`. This matters mainly when the container already exists and is being restarted, because `docker compose up` returns immediately while the backend is still warming up - instantiating [`Account`](./account.md#accountdomainlocalhost-port8080-is_securefalse-backup_foldernone) too quickly will fail to connect. On [Windows](./utils.md#windows) the same value is used to wait between retries after WSL has been restarted. |
|
||||
| `platform` | `str` | No | The platform the image is built for. Defaults to `linux/amd64`. Run `docker buildx ls` to see the platforms your Docker installation supports, and pass the matching value if the default does not build on your machine. |
|
||||
|
||||
Wait time after container has launched:
|
||||
```python
|
||||
from bot import launch_docker_container
|
||||
from status_sdk import launch_docker_container
|
||||
|
||||
# Build from the latest develop branch
|
||||
launch_docker_container(wait_seconds=10)
|
||||
```
|
||||
|
||||
Building from a specific commit:
|
||||
|
||||
```python
|
||||
from status_sdk import launch_docker_container
|
||||
# Pin a specific status-go commit
|
||||
# https://github.com/status-im/status-go/commit/2bee8b6a38cdc8f92d74e2dbb8c4e77fbbeea149
|
||||
launch_docker_container(commit="2bee8b6a38cdc8f92d74e2dbb8c4e77fbbeea149")
|
||||
```
|
||||
|
||||
**Windows Note**: In Docker go to `Settings > Resources > WSL integration` and make sure `Enable integration with my default WSL distro` and `Ubuntu` are **turned on**.
|
||||
Building for a specific platform:
|
||||
|
||||
```bash
|
||||
docker buildx ls
|
||||
```
|
||||
|
||||
```python
|
||||
from status_sdk import launch_docker_container
|
||||
|
||||
# Build for Raspberry PI 5 - arm64
|
||||
launch_docker_container(platform="linux/arm64")
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
In Docker go to `Settings > Resources > WSL integration` and make sure `Enable integration with my default WSL distro` and `Ubuntu` are **turned on**.
|
||||
|
||||

|
||||
|
||||
Docker Desktop creates internal intermediary mounts inside its WSL 2 environment when bind-mounting paths from a WSL distribution into a container. In some cases, these mounts can become **stale**, and the container may fail to start with:
|
||||
|
||||
```
|
||||
error while creating mount source path '/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu/...': file exists
|
||||
```
|
||||
|
||||
The only reliable way to clear the cache is to restart WSL. Method `launch_docker_container` will do the following when the container fails to start:
|
||||
|
||||
1. `wsl --shutdown` is run to clear the stale mounts. Keep in mind that this will shut down **every** WSL distribution, not just the one Docker uses. Any other WSL session running at the same time will be terminated.
|
||||
2. The container is launched again, sleeping `wait_seconds` between each attempt, until it starts.
|
||||
|
||||
WSL boots back up on demand, so no manual step is needed. Docker Desktop does need a moment to bring its backend back up, which is why the retries are spaced out - **increase `wait_seconds` if the container takes a long time to come back**.
|
||||
|
||||
#### Mac
|
||||
|
||||
In Docker go to `Settings > Resources > File Sharing` and make sure the SDK repository is added to **Virtual file shares**.
|
||||
|
||||

|
||||
|
||||
#### Linux
|
||||
|
||||
Make sure the installed `status_sdk` folder has **read and write permissions**, otherwise the container will fail to start or [backups](./account.md#backups) and [profile pictures](./account.md#profile_picture) will not be saved.
|
||||
|
||||
If the package was installed with `sudo` (for example `sudo pip install`), the folder is owned by `root` and your user cannot write to it. Take ownership of the package so your existing permissions apply (adjust the path to where `status_sdk` is installed):
|
||||
|
||||
```bash
|
||||
sudo chown -R $USER:$USER /path/to/status_sdk
|
||||
```
|
||||
|
||||
`$USER` expands to your current login user, so both the owner and group of every file under `status_sdk` are set to you. If the container writes as a different user than the one that installed the package, `chown` alone is not enough - grant read and write permissions to everyone instead:
|
||||
|
||||
```bash
|
||||
sudo chmod -R a+rw /path/to/status_sdk
|
||||
```
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Onchain Agent
|
||||
|
||||
A **personal crypto assistant** that lives inside Status App. The script logs into a Status account, listens for incoming messages **in real time**, and answers them with a [Groq](https://groq.com/) that has been given tools to read and act on the account.
|
||||
|
||||
The agent can look up balances, tokens, contacts and transaction history - and it can also **send messages, send crypto and swap tokens** on your behalf.
|
||||
|
||||
## How it works
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
contact[Your Status Account]
|
||||
subgraph bot[Status AI Agent]
|
||||
listen[listen_messages]
|
||||
agent[LangChain Agent]
|
||||
tools[Status Tools]
|
||||
end
|
||||
llm[Groq LLM]
|
||||
backend[status-im/status-go]
|
||||
|
||||
contact -->|message| listen
|
||||
listen -->|prompt| agent
|
||||
agent <-->|reasoning| llm
|
||||
agent -->|tool call| tools
|
||||
tools <--> backend
|
||||
agent -->|reply| contact
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
Each tool is a thin wrapper around the [Python SDK](../../README.md). They are defined in [`tools.py`](./tools.py), and their arguments are validated by the [pydantic](https://docs.pydantic.dev/) models in [`models.py`](./models.py).
|
||||
|
||||
| Tool | SDK | What the agent can do |
|
||||
|-----|-----|-------------|
|
||||
| `get_balance` | [`balance`](../../docs/account.md#balance) | Read the account's wallet balance, optionally enriched with market data. |
|
||||
| `get_account_info` | [`info`](../../docs/account.md#info) | Read public account details. `password` and `mnemonic` are **excluded**. |
|
||||
| `get_account_contacts` | [`contacts`](../../docs/account.md#contacts) | List contacts, contact requests and group chats. |
|
||||
| `manage_contact` | [`add_contact`](../../docs/account.md#add_contactpublic_key-display_namenone) / `remove_contact` | Accept, send, decline and remove contact requests. |
|
||||
| `get_token_info` | [`get_tokens`](../../docs/account.md#get_tokens) | Look up chains, token symbols and token addresses. |
|
||||
| `search_external_balance` | [`get_balance`](../../docs/account.md#get_balancetoken_addresses-chain_ids1-walletsnone-ccynone) | Read the balance of **any** wallet address, not just the account's. |
|
||||
| `search_messages` | [`get_messages`](../../docs/account.md#get_messageschat_id-start_timestampnone-end_timestampnone) | Read chat history for a date range, including payment requests. |
|
||||
| `search_transactions` | [`get_transactions`](../../docs/account.md#get_transactionsrefreshfalse) | Read historical wallet transactions. |
|
||||
| `send_message` | [`send_message`](../../docs/account.md#send_messagechat_id-message) | **Send a message** to any chat. |
|
||||
| `send_transaction` | [`send_transaction`](../../docs/account.md#send_transactionaddress-symbol-amount-chain_id1) | **Send crypto** to any address. |
|
||||
| `swap_tokens` | [`swap_tokens`](../../docs/account.md#swap_tokensfrom_token-to_token-amount-chain_id1) | **Swap tokens** in the wallet. |
|
||||
|
||||
**Note**: The last three tools move real funds and send real messages. See [Security](./README.md#security).
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install
|
||||
|
||||
From the **repository root**, install the SDK with the `agent` dependencies:
|
||||
|
||||
```
|
||||
pip install ".[agent]"
|
||||
```
|
||||
|
||||
### 2. Configure
|
||||
|
||||
Copy [`env.example`](./env.example) to `.env` in this folder and fill it in:
|
||||
|
||||
```
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
| Variable | What it is |
|
||||
|-----|-------------|
|
||||
| `PASSWORD` | The password of your Status account. |
|
||||
| `NAME` | The [display name](../../docs/account.md#display-name) or ENS name of the account. If you have previously logged in with the SDK you can provide an ENS. For first time log ins, it is best to provide a [display name](../../docs/account.md#display-name). |
|
||||
| `MNEMONIC` | The [recovery phrase](https://status.app/help/profile/understand-your-status-keys-and-recovery-phrase) of the account. Used to recover it into the container. |
|
||||
| `ALCHEMY_TOKEN` | [Alchemy](https://www.alchemy.com/) token - needed for transaction history. |
|
||||
| `COINGECKO_API_KEY` | [CoinGecko](https://www.coingecko.com/) key - needed for token prices. |
|
||||
| `INFURA_TOKEN` | [Infura](https://www.infura.io/) token - needed for Ethereum RPC. |
|
||||
| `GROQ_API_KEY` | [Groq API key](https://console.groq.com/) for the LLM. |
|
||||
| `GROQ_MODEL` | The Groq model name, e.g. `llama-3.3-70b-versatile`. |
|
||||
| `FROM_PUBLIC_KEY` | The public key of the account the bot will **listen and reply to**. This is the account you message the bot *from*. |
|
||||
|
||||
All three wallet keys (`ALCHEMY_TOKEN`, `COINGECKO_API_KEY`, `INFURA_TOKEN`) are required - without all of them the wallet tools raise a custom exception.
|
||||
|
||||
### 3. Run
|
||||
|
||||
The script imports `tools` and `models` as **top-level modules**, so it must be run from inside this folder:
|
||||
|
||||
```
|
||||
cd examples/agents
|
||||
python main.py
|
||||
```
|
||||
|
||||
On the first run, [`launch_docker_container`](../../docs/utils.md#launch_docker_container) builds the Status Backend image, which takes a few minutes. The account is then recovered from `MNEMONIC` and the bot starts listening:
|
||||
|
||||
```
|
||||
[INFO] Running Docker on <your-os-here>
|
||||
[INFO] Successfully logged in!
|
||||
[INFO] Starting messaging
|
||||
[INFO] Messaging launched
|
||||
```
|
||||
|
||||
Now message the bot from the account matching `FROM_PUBLIC_KEY`. It runs until you stop it with `Ctrl+C`.
|
||||
|
||||
## Security
|
||||
|
||||
**This agent has full control of the Status account and its wallet.** It can send messages as you, transfer crypto out of your wallet, and swap your tokens - and it decides to do so based on the output of an LLM.
|
||||
|
||||
The safeguards in this example are deliberately simple:
|
||||
|
||||
- **One sender only.** Messages are ignored unless `latest_message["from"] == FROM_PUBLIC_KEY`. Anyone else messaging the bot is not processed.
|
||||
- **Secrets are withheld from prompts.** `get_account_info` strips `password` and `mnemonic` before the LLM ever sees the account details.
|
||||
|
||||
That is the whole boundary. There is **no** spending limit, no confirmation step and no allowlist of receiver addresses. Anyone who can send messages from `FROM_PUBLIC_KEY` - or anyone who can convince the LLM through a [prompt injection](https://en.wikipedia.org/wiki/Prompt_injection) in the chat content - can move funds.
|
||||
|
||||
Use a **dedicated account with a small balance**. Do not point this at a wallet you care about.
|
||||
@@ -2,15 +2,9 @@ from langchain_groq import ChatGroq
|
||||
from langchain.agents import create_agent
|
||||
from dotenv import load_dotenv
|
||||
from typing import Optional
|
||||
import os, sys
|
||||
import pandas as pd
|
||||
|
||||
# Temp solution until repo it turned into a PyPI library
|
||||
# Add the repo root to sys.path so `bot` is importable when running this
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||||
|
||||
import os
|
||||
import tools
|
||||
from bot import Account, launch_docker_container
|
||||
from status_sdk import Account, launch_docker_container
|
||||
|
||||
class StatusToolKit:
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
tabulate
|
||||
langchain
|
||||
langchain-core
|
||||
langchain-groq
|
||||
python-dotenv
|
||||
@@ -5,7 +5,7 @@ import pandas as pd
|
||||
import datetime
|
||||
|
||||
import models
|
||||
from bot import Account, exceptions
|
||||
from status_sdk import Account, exceptions
|
||||
|
||||
|
||||
class StatusBaseTool(BaseTool):
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=77"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "status-sdk"
|
||||
version = "1.0.0"
|
||||
description = "Private chat. Communities. Multi-chain wallet. Browser. dApps all in one app, powered by SNT."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = "MIT"
|
||||
license-files = ["LICENSE.txt"]
|
||||
authors = [{ name = "Status Research & Development GmbH" }]
|
||||
keywords = ["privacy", "decentralized", "messenger", "ethereum", "blockchain", "cryptocurrency"]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
dependencies = [
|
||||
"requests",
|
||||
"websocket-client",
|
||||
"websockets",
|
||||
"pandas",
|
||||
"pillow",
|
||||
"eth-abi",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
agents = [
|
||||
"tabulate",
|
||||
"langchain",
|
||||
"langchain-core",
|
||||
"langchain-groq",
|
||||
"python-dotenv",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Source = "https://github.com/status-im/status-python-sdk"
|
||||
Issues = "https://github.com/status-im/status-python-sdk/issues"
|
||||
"Status App" = "https://status.app/"
|
||||
"Status Backend" = "https://github.com/status-im/status-go"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["status_sdk"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
status_sdk = ["docker-compose.yaml"]
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
requests
|
||||
websocket-client
|
||||
websockets
|
||||
pandas
|
||||
pillow
|
||||
eth-abi
|
||||
@@ -1,3 +1,5 @@
|
||||
from .account import Account
|
||||
from .utils import launch_docker_container
|
||||
from . import exceptions
|
||||
|
||||
__all__ = ["Account", "launch_docker_container", "exceptions"]
|
||||
@@ -35,7 +35,7 @@ class Account:
|
||||
}
|
||||
__ETH_ADDRESS = "0x0000000000000000000000000000000000000000"
|
||||
|
||||
def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_port: int = 9000, is_secure: bool = False, backup_folder: Optional[str] = None):
|
||||
def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_port: int = 9000, is_secure: bool = False, backup_folder: Optional[str] = None, volume_folder: Optional[str] = None):
|
||||
"""
|
||||
Work with your own Status App account
|
||||
|
||||
@@ -44,7 +44,8 @@ class Account:
|
||||
- `backend_port` - the port to connect to Status Backend. If this is changed, the published port for `backend_port` must be updated to match in `docker-compose.yaml` as well.
|
||||
- `media_port` - the port to connect to Status localhost images. If this is changed, the published port for `media_port` must be updated to match in `docker-compose.yaml` as well.
|
||||
- `is_secure` - if `http` or `https` should be used
|
||||
- `backup_folder` - where backup files will be created and stored
|
||||
- `backup_folder` - where backup files will be created and loaded
|
||||
- `volume_folder` - directory containing the `backups` and `assets` folders mounted into the Status Backend Docker container (folder holding `docker-compose.yaml`). Defaults to this package's own installation folder. Set this when Status Backend is launched from a different `docker-compose.yaml` location, such as a local clone of the repo.
|
||||
"""
|
||||
# Wallet transactions
|
||||
self.__alchemy_token = None
|
||||
@@ -54,16 +55,17 @@ class Account:
|
||||
# Path of the backups in the Docker container for Status Backend
|
||||
self.__docker_backup_folder = "./root/.config/Status/backups"
|
||||
self.__backup_folder = backup_folder
|
||||
# PyPI installation folder
|
||||
sdk_folder = volume_folder if volume_folder else os.path.dirname(__file__)
|
||||
# As the docker-compose.yaml folder is at the moment
|
||||
# NOTE: This might change for initial release
|
||||
self.__backup_sdk_folder = os.path.join(os.path.dirname(os.path.dirname(__file__)), "backups")
|
||||
self.__backup_sdk_folder = os.path.join(sdk_folder, "backups")
|
||||
os.makedirs(self.__backup_sdk_folder, exist_ok=True)
|
||||
|
||||
# Path of where images will be uploaded to Status Backend
|
||||
self.__docker_asset_folder = "./assets"
|
||||
# As the docker-compose.yaml folder is at the moment
|
||||
# NOTE: This might change for initial release
|
||||
self.__assets_local_folder = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets")
|
||||
self.__assets_local_folder = os.path.join(sdk_folder, "assets")
|
||||
os.makedirs(self.__assets_local_folder, exist_ok=True)
|
||||
|
||||
self.__logger = Logger()
|
||||
@@ -1379,7 +1381,7 @@ class Account:
|
||||
folder = self.__backup_folder if self.__backup_folder else self.__backup_sdk_folder
|
||||
|
||||
file_name = self.info["compressed_key"][-6:] + "_user_data.bkp"
|
||||
file_path = os.path.join(folder, self.info["compressed_key"][-6:] + "_user_data.bkp")
|
||||
file_path = os.path.join(folder, file_name)
|
||||
if not os.path.exists(file_path):
|
||||
self.logger.warning(f"Backup file was not found in {folder}")
|
||||
return
|
||||
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
@@ -2,7 +2,7 @@ services:
|
||||
backend:
|
||||
build:
|
||||
context: https://github.com/status-im/status-go.git#${STATUS_GO_REF:-develop}
|
||||
platform: linux/amd64
|
||||
platform: ${STATUS_GO_PLATFORM:-linux/amd64}
|
||||
container_name: status-backend
|
||||
ports:
|
||||
- 8080:8080 # backend_port
|
||||
@@ -0,0 +1,64 @@
|
||||
import shutil, os, subprocess, sys, time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from .logger import Logger
|
||||
from . import exceptions
|
||||
|
||||
def launch_docker_container(commit: Optional[str] = None, wait_seconds: int = 5, platform: str = "linux/amd64"):
|
||||
"""
|
||||
Launch the Status Backend Docker container using `docker-compose.yaml`
|
||||
|
||||
NOTE: On Windows, Docker Desktop caches the Docker volume bind mounts in the WSL
|
||||
virtual machine. When the mounts go stale the container cannot start. WSL is
|
||||
restarted to clear the cache and the container is launched again until it is up.
|
||||
|
||||
Parameters:
|
||||
- `commit` - the commit SHA. If no commit is provided, the latest version is pulled
|
||||
- `wait_seconds` - number of seconds to wait before the code resumes. Sleep prevents calling `class Account` faster than launching the docker container. This only happens when the container already exists and it is must be turned on. On Windows the same value is used to wait between retries after WSL has been restarted.
|
||||
- `platform` - the platform the image is built for. Defaults to `linux/amd64`. Run `docker buildx ls` to see the platforms your Docker installation supports.
|
||||
"""
|
||||
logger = Logger()
|
||||
system = sys.platform
|
||||
is_windows = system == "win32"
|
||||
if not shutil.which("docker"):
|
||||
raise exceptions.DockerError("Please install Docker.")
|
||||
|
||||
logger.info(f"Running Docker on {system}")
|
||||
ref = commit if commit else "develop"
|
||||
DOCKER_COMPOSE_PATH = os.path.join(os.path.dirname(__file__), "docker-compose.yaml")
|
||||
docker_path = DOCKER_COMPOSE_PATH
|
||||
if is_windows:
|
||||
p = Path(DOCKER_COMPOSE_PATH)
|
||||
drive = p.drive.rstrip(":").lower()
|
||||
docker_path = f"/mnt/{drive}/" + "/".join(p.parts[1:])
|
||||
|
||||
cmd = ["env", f"STATUS_GO_REF={ref}", f"STATUS_GO_PLATFORM={platform}", "docker", "compose", "-f", docker_path, "up", "-d", "--build"]
|
||||
|
||||
if is_windows:
|
||||
if not shutil.which("wsl"):
|
||||
raise exceptions.DockerError("Please install wsl - https://learn.microsoft.com/en-us/windows/wsl/install.")
|
||||
cmd.insert(0, "wsl")
|
||||
|
||||
logger.info(f"Running:\n{' '.join(cmd)}")
|
||||
docker_compose_up = lambda: subprocess.run(cmd, cwd=os.path.dirname(DOCKER_COMPOSE_PATH), stderr=subprocess.PIPE, text=True)
|
||||
result = docker_compose_up()
|
||||
|
||||
if result.returncode != 0 and is_windows:
|
||||
logger.warning("Command failed! Restarting wsl...")
|
||||
subprocess.run(["wsl", "--shutdown"])
|
||||
attempt = 1
|
||||
while result.returncode != 0:
|
||||
result = docker_compose_up()
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Container started on attempt {attempt}!")
|
||||
break
|
||||
|
||||
logger.warning(f"Attempt {attempt} failed... Sleeping for {wait_seconds}s")
|
||||
time.sleep(wait_seconds)
|
||||
attempt += 1
|
||||
|
||||
if result.returncode != 0:
|
||||
raise exceptions.DockerError(result.stderr.strip())
|
||||
|
||||
logger.info(f"Docker Container successfully launched! Sleeping for {wait_seconds}s")
|
||||
time.sleep(wait_seconds)
|
||||
Reference in New Issue
Block a user