9 Commits
Author SHA1 Message Date
nyeki 19418db4a4 Release version v1.1.2 2026-01-02 00:38:37 -03:00
nyeki 462a21604b Add reverse proxy endpoint 2026-01-02 00:29:56 -03:00
nyeki 5bb1f42bb3 Add documentation to release.py 2025-07-13 21:52:27 +00:00
nyeki 4ab3efff06 Make script in release.py a constant 2025-07-13 21:46:52 +00:00
nyeki 3373363f43 Update commit messages on release 2025-07-13 19:21:58 +00:00
nyeki 123cdc65e3 Release 1.1.1 version 2025-07-13 16:17:50 -03:00
nyeki 164055dd2d Add healthcheck endpoint to router 2025-07-13 19:13:12 +00:00
nyeki 8dbdc23e52 Release 1.1.0 version 2025-07-13 16:11:25 -03:00
nyeki 446dc4b47f Add healthcheck endpoint 2025-07-13 19:07:12 +00:00
9 changed files with 254 additions and 40 deletions
+1
View File
@@ -0,0 +1 @@
3.13
Generated
+105 -19
View File
@@ -84,16 +84,19 @@ dependencies = [
[[package]] [[package]]
name = "api" name = "api"
version = "1.0.0" version = "1.1.2"
dependencies = [ dependencies = [
"axum", "axum",
"bitcode", "bitcode",
"clap", "clap",
"dedent", "dedent",
"dotenvy", "dotenvy",
"futures-util",
"hyper",
"reqwest", "reqwest",
"serde", "serde",
"tokio", "tokio",
"tokio-util",
"turbostore", "turbostore",
"urlencoding", "urlencoding",
] ]
@@ -123,7 +126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes 1.10.1",
"form_urlencoded", "form_urlencoded",
"futures-util", "futures-util",
"http", "http",
@@ -156,7 +159,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"http", "http",
"http-body", "http-body",
@@ -233,6 +236,22 @@ version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
dependencies = [
"byteorder",
"iovec",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.1" version = "1.10.1"
@@ -419,6 +438,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "futures"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"
@@ -434,6 +459,23 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@@ -453,9 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab",
"tokio-io",
] ]
[[package]] [[package]]
@@ -500,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes 1.10.1",
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -530,7 +578,7 @@ version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"fnv", "fnv",
"itoa", "itoa",
] ]
@@ -541,7 +589,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"http", "http",
] ]
@@ -551,7 +599,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"http", "http",
"http-body", "http-body",
@@ -572,13 +620,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.6.0" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [ dependencies = [
"bytes", "atomic-waker",
"bytes 1.10.1",
"futures-channel", "futures-channel",
"futures-util", "futures-core",
"h2", "h2",
"http", "http",
"http-body", "http-body",
@@ -586,6 +635,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"pin-utils",
"smallvec", "smallvec",
"tokio", "tokio",
"want", "want",
@@ -613,7 +663,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-util", "hyper-util",
@@ -630,7 +680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes 1.10.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -801,6 +851,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.11.0" version = "2.11.0"
@@ -1056,9 +1115,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes 1.10.1",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
"futures-util",
"h2", "h2",
"http", "http",
"http-body", "http-body",
@@ -1080,12 +1140,14 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-util",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams",
"web-sys", "web-sys",
] ]
@@ -1396,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes 1.10.1",
"io-uring", "io-uring",
"libc", "libc",
"mio", "mio",
@@ -1407,6 +1469,17 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"futures",
"log",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.5.0" version = "2.5.0"
@@ -1440,11 +1513,11 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"pin-project-lite", "pin-project-lite",
@@ -1474,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes 1.10.1",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
@@ -1676,6 +1749,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.77" version = "0.3.77"
+5 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "api" name = "api"
version = "1.0.0" version = "1.1.2"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -9,8 +9,11 @@ bitcode = "0.6.6"
clap = { version = "4.5.41", features = ["derive", "env"] } clap = { version = "4.5.41", features = ["derive", "env"] }
dedent = "0.1.1" dedent = "0.1.1"
dotenvy = "0.15.7" dotenvy = "0.15.7"
reqwest = { version = "0.12.22", features = ["json"] } futures-util = { version = "0.3.31", features = ["tokio-io"] }
hyper = "1.8.1"
reqwest = { version = "0.12.22", features = ["json", "stream"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
tokio-util = { version = "0.7.17", features = ["io"] }
turbostore = "0.2.0" turbostore = "0.2.0"
urlencoding = "2.1.3" urlencoding = "2.1.3"
+6
View File
@@ -0,0 +1,6 @@
def main():
print("Hello from api!")
if __name__ == "__main__":
main()
+12
View File
@@ -0,0 +1,12 @@
[project]
name = "api"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
[dependency-groups]
dev = [
"toml>=0.10.2",
]
+26 -14
View File
@@ -1,35 +1,47 @@
"""
A release script to build and tag docker files, commit changes, and create release tags.
It requires `toml` to be installed (in Ubuntu via `sudo apt install python3 python3-toml` and then
`python3 release.py`).
Bump the version manually in Cargo.toml before running this script. You'll be asked for
confirmation before running all the steps, so you're safe if you forget.
"""
import os import os
import toml import toml
def get_release_version(): def get_release_version():
""" """
Reads the version from the Cargo.toml file. Reads the version from the Cargo.toml file.
""" """
cargo_toml_path = os.path.join(os.path.dirname(__file__), 'Cargo.toml') cargo_toml_path = os.path.join(os.path.dirname(__file__), "Cargo.toml")
with open(cargo_toml_path, 'r') as f: with open(cargo_toml_path, "r", encoding="UTF-8") as f:
cargo_toml = toml.load(f) cargo_toml = toml.load(f)
return cargo_toml['package']['version'] return cargo_toml["package"]["version"]
version = get_release_version() version = get_release_version()
input(f"The current version is v{version}. Is this properly bumped? (Enter/Ctrl+C)") input(f"The current version is v{version}. Is this properly bumped? (Enter/Ctrl+C)")
script = f""" SCRIPT = f"""
docker build -t registry.nyeki.dev/nyeki/api:latest . docker build -t registry.nyeki.dev/nyeki/api:latest .
docker push registry.nyeki.dev/nyeki/api:latest docker push registry.nyeki.dev/nyeki/api:latest
docker tag registry.nyeki.dev/nyeki/api:latest registry.nyeki.dev/nyeki/api:v{version} docker tag registry.nyeki.dev/nyeki/api:latest registry.nyeki.dev/nyeki/api:v{version}
docker push registry.nyeki.dev/nyeki/api:v{version} docker push registry.nyeki.dev/nyeki/api:v{version}
git add . git add .
git commit -m "Release {version} version" git commit -m "Release version v{version}"
git tag -a v{version} -m "Release version {version}" git tag -a v{version} -m "Release version v{version}"
git push origin main git push origin main
git push origin v{version} git push origin v{version}
""" """
os.system(script) os.system(SCRIPT)
print(f"v{version} was released. Congratulations!") print(f"v{version} was released. Congratulations!")
+20 -2
View File
@@ -10,6 +10,7 @@ pub mod v1;
pub fn router() -> Router<Arc<State>> { pub fn router() -> Router<Arc<State>> {
Router::new() Router::new()
.route("/", get(docs)) .route("/", get(docs))
.route("/healthcheck", get(healthcheck))
.nest("/v1", v1::router()) .nest("/v1", v1::router())
} }
@@ -17,8 +18,9 @@ async fn docs() -> &'static str {
dedent!( dedent!(
" "
Welcome to Nyeki's API! This is a tiny API I built to provide some basic information I Welcome to Nyeki's API! This is a tiny API I built to provide some basic information I
found myself needing in more than one place. This at the moment only includes Discord and found myself needing in more than one place. This at the moment only includes Discord
GitHub profiles, but I may add more stuff as I see myself needing it. profiles, GitHub profiles, and a reverse proxy, but I may add more stuff as I see myself
needing it.
The API is versioned, so API endpoints will always be prefixed with `/v{version}`. The The API is versioned, so API endpoints will always be prefixed with `/v{version}`. The
current version is 1 (i.e. stuff is prefixed by `/v1`). All responses are JSON (or current version is 1 (i.e. stuff is prefixed by `/v1`). All responses are JSON (or
@@ -31,6 +33,9 @@ async fn docs() -> &'static str {
The current endpoints are: The current endpoints are:
GET /healthcheck
Returns OK when the API is running.
GET /v1/profile GET /v1/profile
Return's the user's profile (username, name, avatar, banner, and URL). Return's the user's profile (username, name, avatar, banner, and URL).
@@ -84,6 +89,15 @@ async fn docs() -> &'static str {
for supported sources). for supported sources).
- `source`: The source of the profile (either `discord` or `github`). - `source`: The source of the profile (either `discord` or `github`).
GET /v1/proxy
Proxies a request to the given URL.
Note that headers are not forwarded and neither is the request body. This is a
simple GET proxy.
Query parameters:
- `url`: The URL to proxy the request to.
They all take 2 query parameters: `id` and `source`. The `id` is the ID of the user you They all take 2 query parameters: `id` and `source`. The `id` is the ID of the user you
want to get the profile of, and the `source` is the source of the profile. The source can want to get the profile of, and the `source` is the source of the profile. The source can
be either `discord` or `github`. be either `discord` or `github`.
@@ -113,3 +127,7 @@ async fn docs() -> &'static str {
" "
) )
} }
async fn healthcheck() -> &'static str {
"OK"
}
+50 -1
View File
@@ -4,10 +4,12 @@ use std::sync::Arc;
use axum::{ use axum::{
Json, Router, Json, Router,
body::{Body, Bytes},
extract::{Query, State as AxumState}, extract::{Query, State as AxumState},
response::Redirect, response::{Redirect, Response},
routing::get, routing::get,
}; };
use futures_util::StreamExt;
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use turbostore::{Decode, Duration, Encode}; use turbostore::{Decode, Duration, Encode};
@@ -21,6 +23,7 @@ pub fn router() -> Router<Arc<State>> {
.route("/profile/avatar", get(get_profile_avatar)) .route("/profile/avatar", get(get_profile_avatar))
.route("/profile/banner", get(get_profile_banner)) .route("/profile/banner", get(get_profile_banner))
.route("/profile/url", get(get_profile_url)) .route("/profile/url", get(get_profile_url))
.route("/proxy", get(proxy_with_cors))
.fallback(error_404) .fallback(error_404)
.method_not_allowed_fallback(error_405) .method_not_allowed_fallback(error_405)
} }
@@ -300,3 +303,49 @@ async fn get_profile_url(
Ok(Redirect::to(&profile.url)) Ok(Redirect::to(&profile.url))
} }
#[derive(Debug, Deserialize)]
struct ProxyQuery {
url: String,
}
async fn proxy_with_cors(Query(params): Query<ProxyQuery>) -> Result<Response, Error> {
let client = Client::new();
match client.get(&params.url).send().await {
Ok(resp) => {
let status = resp.status();
let headers = resp.headers().clone();
// Turn the reqwest response into a streaming body
let stream = resp.bytes_stream().map(|chunk_result| match chunk_result {
Ok(chunk) => Ok::<Bytes, std::io::Error>(chunk),
Err(_) => Err(std::io::Error::other("stream error")),
});
// Convert the stream into a hyper Body
let body = Body::from_stream(stream);
// Build response
let mut response = Response::builder().status(status);
let headers_mut = response.headers_mut().unwrap();
for (key, value) in headers.iter() {
// Skip certain headers that hyper will manage itself
if key.as_str().eq_ignore_ascii_case("content-length") {
continue;
}
headers_mut.insert(key, value.clone());
}
// Insert CORS headers
headers_mut.insert("access-control-allow-origin", "*".parse().unwrap());
headers_mut.insert("access-control-allow-methods", "GET, OPTIONS".parse().unwrap());
headers_mut.insert("access-control-allow-headers", "*".parse().unwrap());
Ok(response.body(body).unwrap())
}
Err(_) => Err(Error {
status: 500,
message: "Failed to fetch the requested URL".into(),
}),
}
}
Generated
+27
View File
@@ -0,0 +1,27 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "api"
version = "0.1.0"
source = { virtual = "." }
[package.dev-dependencies]
dev = [
{ name = "toml" },
]
[package.metadata]
[package.metadata.requires-dev]
dev = [{ name = "toml", specifier = ">=0.10.2" }]
[[package]]
name = "toml"
version = "0.10.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
]