Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@
"editor.rulers": [80],
},
"files.eol": "\n",
"cSpell.words": [
"Airweave"
],
}
126 changes: 89 additions & 37 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,46 @@
This module is an Arweave FastAPI server that allows users to
communicate with Arweave, and put Arkly files on chain.
"""
import logging
import time
from typing import Final, List

from fastapi import FastAPI, File, Form, Request, Response, UploadFile
from fastapi import FastAPI, File, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse

from middleware import _update_db
from models import ArweaveTransaction
from models import Tags
from primary_functions import (
_all_transactions,
_check_balance,
_check_balance_form,
_check_last_transaction,
_check_transaction_status,
_create_transaction,
_create_transaction_form,
_estimate_transaction_cost,
_fetch_tx_metadata,
_fetch_upload,
_retrieve_by_tag_pair,
_validate_bag,
)

logging.basicConfig(
format="%(asctime)-15s %(levelname)s :: %(filename)s:%(lineno)s:%(funcName)s() :: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level="INFO",
)

logging.Formatter.converter = time.gmtime

logger = logging.getLogger(__name__)

# Arkly-arweave API description.
API_DESCRIPTION: Final[str] = " "

# OpenAPI tags delineating the documentation.
TAG_ARWEAVE: Final[str] = "arweave"
TAG_ARWEAVE_WALLET: Final[str] = "arweave wallet"
TAG_ARWEAVE_SEARCH: Final[str] = "arweave search"
TAG_ARKLY: Final[str] = "arkly"

# Metadata for each of the tags in the OpenAPI specification. To order
Expand All @@ -37,6 +52,14 @@
"name": TAG_ARWEAVE,
"description": "Manage Arweave transactions",
},
{
"name": TAG_ARWEAVE_WALLET,
"description": "Manage Arweave wallets",
},
{
"name": TAG_ARWEAVE_SEARCH,
"description": "Search for Arweave transactions",
},
{
"name": TAG_ARKLY,
"description": "Arkly functions on-top of Arweave",
Expand All @@ -46,7 +69,7 @@
app = FastAPI(
title="api.arkly.io",
description=API_DESCRIPTION,
version="2022.11.02.0001",
version="2023.08.09.0002",
contact={
"": "",
},
Expand Down Expand Up @@ -79,64 +102,93 @@ def redirect_root_to_docs():
return RedirectResponse(url="/docs")


@app.post("/check_balance/", tags=[TAG_ARWEAVE])
async def check_balance(file: UploadFile = File(...)):
@app.post("/check_wallet_balance/", tags=[TAG_ARWEAVE_WALLET])
async def check_wallet_balance(wallet: UploadFile):
"""Allows a user to check the balance of their wallet."""
return await _check_balance(file)
return await _check_balance(wallet)


@app.post("/check_balance_form/", tags=[TAG_ARWEAVE])
async def check_balance_form(wallet: str = Form()):
"""Allows a user to check the balance of their wallet."""
return await _check_balance_form(wallet)
@app.post("/check_wallet_last_transaction/", tags=[TAG_ARWEAVE_WALLET])
async def check_wallet_last_transaction(wallet: UploadFile):
"""Allows a user to check the transaction ID of their last
transaction.
"""
return await _check_last_transaction(wallet)


@app.post("/check_last_transaction/", tags=[TAG_ARWEAVE])
async def check_last_transaction(file: UploadFile = File(...)):
"""Allows a user to check the transaction id of their last
transaction.
@app.get("/estimate_transaction_cost/", tags=[TAG_ARWEAVE])
async def estimate_transaction_cost(size_in_bytes: str):
"""Allows a user to get an estimate of how much a transaction may
cost.
"""
return await _check_last_transaction(file)
return await _estimate_transaction_cost(size_in_bytes)


@app.get("/check_transaction_status/", tags=[TAG_ARWEAVE])
async def check_transaction_status(transaction_id: str):
"""Allows a user to check the transaction id of their last
transaction.

Example Tx: `rYa3ILXqWi_V52xPoG70y2EupPsTtu4MsMmz6DI4fy4`
"""
return await _check_transaction_status(transaction_id)


@app.get("/estimate_transaction_cost/", tags=[TAG_ARWEAVE])
async def estimate_transaction_cost(size_in_bytes: str):
"""Allows a user to get an estimate of how much a transaction may
cost.
@app.get("/fetch_transaction/", tags=[TAG_ARWEAVE])
async def fetch_transaction(transaction_id: str):
"""Allows a user to read their transaction files from the Arweave
blockchain.

Example Tx: `rYa3ILXqWi_V52xPoG70y2EupPsTtu4MsMmz6DI4fy4`
"""
return _estimate_transaction_cost(size_in_bytes)
return await _fetch_upload(transaction_id)


@app.get("/fetch_upload/", tags=[TAG_ARWEAVE])
async def fetch_upload(transaction_id: str):
"""Allows a user to read their file upload from the Arweave
blockchain.
@app.get("/fetch_transaction_metadata/", tags=[TAG_ARWEAVE])
async def fetch_transaction_metadata(transaction_id: str):
"""Fetch metadata from a given transaction ID to provide further
information about the uploaded package.

Example Tx: `rYa3ILXqWi_V52xPoG70y2EupPsTtu4MsMmz6DI4fy4`
"""
return await _fetch_upload(transaction_id)
return await _fetch_tx_metadata(transaction_id)


@app.post("/create_transaction/", tags=[TAG_ARKLY])
async def create_transaction(files: List[UploadFile] = File(...)):
"""Create an Arkly package and Arweave transaction."""
return await _create_transaction(files)
@app.get("/all_wallet_transactions/", tags=[TAG_ARWEAVE_SEARCH])
async def get_all_wallet_transactions(wallet_addr: str):
"""Allows a user to see a list of all transactions with a given
wallet.

Example wallet: `6KymaAPWd3JNyMT0B7EPYij4TWxehhMrzRD8qifCSLs`
"""
return await _all_transactions(wallet_addr)


@app.get("/transactions_by_tag_pair/", tags=[TAG_ARWEAVE_SEARCH])
async def get_transactions_by_tag_pair(name: str, value: str):
"""Allows a user to retrieve transactions by tag-pair.

Example tag key: `x-tag`
Example tag value: `arkly hello world!`
"""
return await _retrieve_by_tag_pair(name, value)

@app.post("/create_transaction_form/", tags=[TAG_ARKLY])
async def create_transaction_form(transaction_json: ArweaveTransaction):

@app.post("/create_transaction/", tags=[TAG_ARKLY])
async def create_transaction(
wallet: UploadFile,
package_file_name: str,
files: List[UploadFile] = File(...),
tags: Tags | None = None,
):
"""Create an Arkly package and Arweave transaction."""
data_files = await _create_transaction_form(transaction_json)
return await _create_transaction(data_files)
return await _create_transaction(wallet, files, package_file_name, tags)


@app.get("/validate_arweave_bag/", tags=[TAG_ARKLY])
@app.get("/validate_arkly_bag/", tags=[TAG_ARKLY])
async def validate_bag(transaction_id: str, response: Response):
"""Given an Arweave transaction ID, Validate an Arkly link as a bag."""
"""Given an Arweave transaction ID, Validate an Arkly link as a bag.

Example Tx: `rYa3ILXqWi_V52xPoG70y2EupPsTtu4MsMmz6DI4fy4`
"""
return await _validate_bag(transaction_id, response)
6 changes: 4 additions & 2 deletions middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
validation, and responses to be intercepted and augmented, e.g. adding
headers, and other information.
"""

import logging
from typing import Callable

import psycopg2
from fastapi import Request

logger = logging.getLogger(__name__)


async def _update_db(request: Request, call_next: Callable):
"""Update the database by one per endpoint called."""
Expand Down Expand Up @@ -44,6 +46,6 @@ async def _update_db(request: Request, call_next: Callable):
connection.commit()
cursor.close()
except psycopg2.DatabaseError as error:
print(error)
logger.warning("Postgres may not be configured correctly: %s", error)
response = await call_next(request)
return response
73 changes: 73 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""FastAPI models used in the Arweave API."""
import json
from typing import List

from pydantic import BaseModel
Expand All @@ -21,3 +22,75 @@ class ArweaveTransaction(BaseModel):

ArweaveKey: str
ArweaveFiles: List[FileItem]


class Tag(BaseModel):
"""Describes the structure of a single tag for upload to Arweave. A
tag is simply a HTTP header and consists of a name and value.

E.g. Name = `Content-type`, value = `application/gzip`
becomes `"Content-type: application/gzip"`.
"""

name: str
value: str


# The Tags data type provides a way to provide an extensible list of
# data values, in this case, header tags for Airweave, where native
# HTTP handling makes this difficult as the form (from the FastAPI docs:
# is encoded as `application/x-www-form-urlencoded`.
#
# See also:
#
# * https://github.com/tiangolo/fastapi/issues/2257#issuecomment-727036089
# * https://stackoverflow.com/a/70640522/21120938
# * https://docs.pydantic.dev/1.10/usage/types/#classes-with-__get_validators__
#
class Tags(BaseModel):
"""Tags is an extensible data-type that allows users to provide
zero-to-many tags to supply to Airweave.

To provide a value, provide a JSON object that looks something like
as follows:

```json
{
"tags": [
{
"name": "tag_name_1",
"value": "tag_value_1"
},
{
"name": "tag_name_2",
"value": "tag_value_2"
},
{
"name": "tag_name_3",
"value": "tag_value_3"
}
]
}
```

"""

# Default values are provided to help users understand how to use
# this data type.
#
# To create a Tag object you can do the following:
# Tag(**json.loads('{"name": "tag_name_1", "value": "tag_value_1"}')),
#
tags: list[Tag] = []

@classmethod
def __get_validators__(cls):
# pylint: disable=C0202
yield cls.validate_to_json

@classmethod
def validate_to_json(cls, value):
"""Parse the input parameters and return a Tags instance."""
if isinstance(value, str):
return cls(**json.loads(value))
return value
Loading