Merge pull request #1 from slyons/dev/pylib
Adding Python library to client
234
.github/workflows/CI.yaml
vendored
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# This file is autogenerated by maturin v1.7.0
|
||||
# To update, run
|
||||
#
|
||||
# maturin generate-ci github
|
||||
#
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit_test:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- run: cargo test --all-features
|
||||
rust_integration_test:
|
||||
name: Rust Integration test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: cd tests/rust-test && ./test.sh
|
||||
python_integration_test:
|
||||
name: Python Integration test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: getsentry/action-setup-venv@v2.1.0
|
||||
id: venv
|
||||
with:
|
||||
python-version: 3.10.7
|
||||
- run: cd tests/python-sync && ./test.sh
|
||||
linux:
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./szurubooru-client
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- runner: ubuntu-latest
|
||||
target: x86_64
|
||||
- runner: ubuntu-latest
|
||||
target: x86
|
||||
- runner: ubuntu-latest
|
||||
target: aarch64
|
||||
- runner: ubuntu-latest
|
||||
target: armv7
|
||||
- runner: ubuntu-latest
|
||||
target: ppc64le
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: -F python --release --out dist --find-interpreter -m szurubooru-client/Cargo.toml
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
before-script-linux: |
|
||||
# If we're running on rhel centos, install needed packages.
|
||||
if command -v yum &> /dev/null; then
|
||||
yum update -y && yum install -y perl-core openssl openssl-devel pkgconfig libatomic
|
||||
|
||||
# If we're running on i686 we need to symlink libatomic
|
||||
# in order to build openssl with -latomic flag.
|
||||
if [[ ! -d "/usr/lib64" ]]; then
|
||||
ln -s /usr/lib/libatomic.so.1 /usr/lib/libatomic.so
|
||||
fi
|
||||
else
|
||||
# If we're running on debian-based system.
|
||||
apt update -y && apt-get install -y libssl-dev openssl pkg-config
|
||||
fi
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-linux-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
|
||||
musllinux:
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./szurubooru-client
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- runner: ubuntu-latest
|
||||
target: x86_64
|
||||
- runner: ubuntu-latest
|
||||
target: x86
|
||||
- runner: ubuntu-latest
|
||||
target: aarch64
|
||||
- runner: ubuntu-latest
|
||||
target: armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: -F python --release --out dist --find-interpreter -m szurubooru-client/Cargo.toml
|
||||
sccache: 'true'
|
||||
manylinux: musllinux_1_2
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-musllinux-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./szurubooru-client
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- runner: windows-latest
|
||||
target: x64
|
||||
- runner: windows-latest
|
||||
target: x86
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
architecture: ${{ matrix.platform.target }}
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: -F python --release --out dist --find-interpreter -m szurubooru-client/Cargo.toml
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-windows-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./szurubooru-client
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- runner: macos-12
|
||||
target: x86_64
|
||||
- runner: macos-14
|
||||
target: aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: -F python --release --out dist --find-interpreter -m szurubooru-client/Cargo.toml
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-macos-${{ matrix.platform.target }}
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./szurubooru-client
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist -m szurubooru-client/Cargo.toml
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-sdist
|
||||
path: dist
|
||||
|
||||
python_release:
|
||||
name: Python Release
|
||||
runs-on: ubuntu-latest
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, musllinux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --non-interactive --skip-existing wheels-*/*
|
||||
crate_release:
|
||||
name: Crates.io Release
|
||||
runs-on: ubuntu-latest
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, musllinux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cargo publish
|
||||
run: cargo publish --token ${CRATES_TOKEN}
|
||||
env:
|
||||
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}
|
||||
4
.gitignore
vendored
|
|
@ -5,3 +5,7 @@ szurubooru-integration-test/szurubooru/data/*
|
|||
szurubooru-integration-test/szurubooru/pgsql/*
|
||||
szurubooru-integration-test/szurubooru/server/*
|
||||
**/.DS_Store
|
||||
**/target
|
||||
*.so
|
||||
*.pyc
|
||||
/docs/_build/**
|
||||
|
|
|
|||
18
CHANGELOG.md
|
|
@ -1,3 +1,21 @@
|
|||
## v0.5.0 (2024-09-05)
|
||||
|
||||
### Feat
|
||||
|
||||
- **all**: adding python library wrapper
|
||||
|
||||
### Fix
|
||||
|
||||
- **python**: refactoring python client
|
||||
- **cargo**: adding vendored openssl to help with build
|
||||
- **python-client**: fixing paged search results to use a PyList instead
|
||||
|
||||
### Refactor
|
||||
|
||||
- **python**: adding custom python modules to possibly help with documentation
|
||||
- **models**: removing unnecessary python classes
|
||||
- **models**: saving some changes in prep of python library
|
||||
|
||||
## v0.4.0 (2024-08-12)
|
||||
|
||||
### Feat
|
||||
|
|
|
|||
235
Cargo.lock
generated
|
|
@ -150,6 +150,28 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
|
|
@ -632,6 +654,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
|
|
@ -702,6 +730,15 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
|
|
@ -846,6 +883,15 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.3.1+3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.103"
|
||||
|
|
@ -854,6 +900,7 @@ checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
|
|||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
|
@ -887,12 +934,59 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
|
|
@ -931,6 +1025,12 @@ version = "0.3.30"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
|
|
@ -949,6 +1049,72 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
"serde",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
|
|
@ -1102,6 +1268,19 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"sha1",
|
||||
"szurubooru-client",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
|
@ -1213,18 +1392,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.205"
|
||||
version = "1.0.208"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
|
||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.205"
|
||||
name = "serde-pyobject"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
|
||||
checksum = "ca4b0aad8b225845739a0030a0d5cc2ae949c56a86a7daf9226c7df7c2016d16"
|
||||
dependencies = [
|
||||
"pyo3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.208"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1290,6 +1479,12 @@ version = "2.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
@ -1357,9 +1552,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.72"
|
||||
version = "2.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1395,7 +1590,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "szurubooru-client"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
|
|
@ -1404,8 +1599,11 @@ dependencies = [
|
|||
"futures-util",
|
||||
"hex",
|
||||
"mockito",
|
||||
"openssl",
|
||||
"pyo3",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde-pyobject",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"strum",
|
||||
|
|
@ -1418,17 +1616,10 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "szurubooru-integration-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"sha1",
|
||||
"szurubooru-client",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
|
|
@ -1682,6 +1873,12 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
|
|
|||
|
|
@ -2,5 +2,7 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"szurubooru-client",
|
||||
"szurubooru-integration-test"
|
||||
#"szurubooru-integration-test",
|
||||
"tests/rust-test"
|
||||
]
|
||||
default-members = ["szurubooru-client"]
|
||||
|
|
|
|||
20
LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2024 Scott Lyons
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
28
docs/Makefile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
serve:
|
||||
python -m http.server -d $(BUILDDIR)/html
|
||||
|
||||
clean:
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
pip uninstall -y szurubooru_client
|
||||
maturin develop -F python -m ../szurubooru-client/Cargo.toml
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
63
docs/client.rst
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
Szurubooru Clients
|
||||
==================
|
||||
|
||||
These are the clients available for interacting with Szurubooru. There are two clients, ``SzurubooruSyncClient`` and ``SzurubooruAsyncClient`` . The only difference
|
||||
between the two of them is that ``SzurubooruAsyncClient`` is compatible with ``asyncio`` for use in ``async`` scripts.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
Sync Client
|
||||
-----------
|
||||
|
||||
.. autoclass:: szurubooru_client.SzurubooruSyncClient
|
||||
|
||||
|
||||
Async Client
|
||||
------------
|
||||
|
||||
.. autoclass:: szurubooru_client.SzurubooruAsyncClient
|
||||
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
All exceptions that are thrown by the client are instances of the ``SzuruClientError`` class. It's a tuple of two items: An exception type (as a string) and a string with more details
|
||||
about the exception.
|
||||
|
||||
.. autoexception:: szurubooru_client.SzuruClientError
|
||||
|
||||
.. _rver:
|
||||
|
||||
Resource Versioning
|
||||
-------------------
|
||||
|
||||
Many of the ``update`` and other methods on the client classes accept a ``version`` argument. This is part of Szurubooru's optimistic locking. Each resource has its ``version`` retuned
|
||||
to the client through any of the ``get`` methods. This value must be provided to any of the ``update`` or ``delete`` methods. If the version doesn't match at the time
|
||||
(due to a modified resource) then the call will fail.
|
||||
|
||||
Pagination
|
||||
----------
|
||||
|
||||
Most of the ``list`` methods on the clients return a paged object that holds the results along with other details about the query:
|
||||
|
||||
.. autoclass:: szurubooru_client.PagedResult
|
||||
|
||||
Paging through the results of ``list`` methods are done using two parameters:
|
||||
|
||||
.. _limits:
|
||||
|
||||
Result Limits
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Most of the ``list`` methods on the clients support a ``limit`` parameter. This parameter limits the number of resources returned by the method as part of
|
||||
a :class:`~szurubooru_client.PagedResult` object
|
||||
|
||||
.. _offsets:
|
||||
|
||||
Result Offsets
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Most of the ``list`` methods on the clients support a ``offset`` parameter. This parameter skips through the results returned by the method as part of
|
||||
a :class:`~szurubooru_client.PagedResult` object
|
||||
41
docs/conf.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'Szurubooru Client'
|
||||
copyright = '2024, Scott Lyons'
|
||||
author = 'Scott Lyons'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'myst_parser',
|
||||
'sphinx.ext.autodoc',
|
||||
"sphinx_codefence"
|
||||
]
|
||||
|
||||
autodoc_default_options = {
|
||||
'members': True,
|
||||
'show-inheritance': True,
|
||||
}
|
||||
autodoc_typehints = "description"
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
source_suffix = {
|
||||
'.rst': 'restructuredtext',
|
||||
'.txt': 'markdown',
|
||||
'.md': 'markdown',
|
||||
}
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'alabaster'
|
||||
html_static_path = ['_static']
|
||||
113
docs/examples.rst
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
========
|
||||
Examples
|
||||
========
|
||||
|
||||
.. contents:: Table of Contents
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
All of these examples are taken from the ``python-sync`` script in the ``tests`` directory of the repo.
|
||||
|
||||
Before any of the examples will work, you need to import the right classes and functions:
|
||||
|
||||
```python
|
||||
from szurubooru_client import *
|
||||
from szurubooru_client.tokens import *
|
||||
from szurubooru_client.models import *
|
||||
```
|
||||
|
||||
Creating a client
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
client = SzurubooruSyncClient("http://localhost:9802", username="integration_user",
|
||||
password="integration_password", allow_insecure=True)
|
||||
```
|
||||
|
||||
Creating a new tag
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
foo_tag = client.create_tag("foo", category="default", description="The foo tag")
|
||||
assert foo_tag.names == ["foo"]
|
||||
```
|
||||
|
||||
Returning only a subset of fields
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
# Omit the "description" field
|
||||
tags = client.list_tags(fields=["version", "names", "category"])
|
||||
assert tags.results[0].description is None
|
||||
```
|
||||
|
||||
Uploading from a file path
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
folly1 = client.create_post(file_path="../folly1.jpg",
|
||||
tags=["maine_coon", "cat", "folly1"],
|
||||
safety=PostSafety.Safe)
|
||||
```
|
||||
|
||||
Searching for an existing post using an image
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
folly1_search = client.post_for_image("../folly1.jpg")
|
||||
assert folly1_search is not None
|
||||
```
|
||||
|
||||
Querying by an anonymous tag
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
cat_posts = client.list_posts(query=[anonymous_token("cat")])
|
||||
```
|
||||
|
||||
Querying by a named tag
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
mc_posts = client.list_posts(query=[named_token(PostNamedToken.Tag, "maine_coon")])
|
||||
```
|
||||
|
||||
Pagination
|
||||
^^^^^^^^^^
|
||||
|
||||
```python
|
||||
posts = client.list_posts(limit=1)
|
||||
assert posts.total == 4
|
||||
assert len(posts.results) == 1
|
||||
|
||||
posts2 = client.list_posts(limit=1, offset=1)
|
||||
assert posts.results != posts2.results
|
||||
```
|
||||
|
||||
Commenting on a post
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
cat_results = client.list_posts([anonymous_token("cat")])
|
||||
post_id = cat_results.results[0].id
|
||||
|
||||
comment = client.create_comment("Excellent cat!", post_id)
|
||||
```
|
||||
|
||||
Getting all comments for a post
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
comment_list = client.list_comments([named_token(CommentNamedToken.Post, post_id)])
|
||||
assert len(comment_list.results) != 0
|
||||
```
|
||||
|
||||
Downloading an image to a local path
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
```python
|
||||
cat_results = client.list_posts([anonymous_token("cat")])
|
||||
post_id = cat_results.results[0].id
|
||||
|
||||
client.download_image_to_path(post_id, "/tmp/cat.jpg")
|
||||
```
|
||||
15
docs/fields.rst
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Field selection
|
||||
===============
|
||||
|
||||
The Szurubooru API supports *Field Selection*. This allows you to select only a subset of fields to return from the API. By default all fields are selected and returned, but if you
|
||||
pass in a list of field names to any of the client methods that support it, only those fields will be populated in the models.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```python
|
||||
tags = client.list_tags(fields=["version", "names", "category"])
|
||||
assert tags.results[0].description is None
|
||||
```
|
||||
|
||||
Because the ``description`` field was not specified, it's set to ``None``
|
||||
15
docs/index.rst
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Szurubooru Client documentation
|
||||
===============================
|
||||
|
||||
Welcome to the documentation for ``szurubooru_client`` . This library is a wrapper around a Rust library for interacting with the self-hosted
|
||||
image site `Szurubooru <https://github.com/rr-/szurubooru/tree/master>`_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
client
|
||||
tokens
|
||||
models
|
||||
fields
|
||||
examples
|
||||
35
docs/make.bat
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
10
docs/models.rst
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Szurubooru Models
|
||||
=================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
.. automodule:: szurubooru_client.models
|
||||
:members:
|
||||
:undoc-members:
|
||||
830
docs/tokens.rst
Normal file
|
|
@ -0,0 +1,830 @@
|
|||
Szurubooru Query Tokens
|
||||
=======================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
Most of the ``list`` methods on the client support a ``query`` parameter. This parameter (a ``list`` of tokens) allows you to filter the results returned by the API.
|
||||
|
||||
There's four kinds of tokens: ``Named``, ``Sort``, ``Anonymous`` and ``Special``.
|
||||
Because this library is a wrapper around a Rust library, there's a group of type-safe ``Enum`` objects that allow you to
|
||||
make sure you get the right field names when constructing the tokens.
|
||||
|
||||
All tokens can be mixed and matched to filter the results how you'd prefer.
|
||||
|
||||
|
||||
.. _named-tokens:
|
||||
|
||||
|
||||
Named tokens
|
||||
^^^^^^^^^^^^
|
||||
|
||||
.. autofunction:: szurubooru_client.tokens.named_token
|
||||
|
||||
.. _sort-tokens:
|
||||
|
||||
|
||||
Sort tokens
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. autofunction:: szurubooru_client.tokens.sort_token
|
||||
|
||||
|
||||
Anonymous tokens
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autofunction:: szurubooru_client.tokens.anonymous_token
|
||||
|
||||
|
||||
Special tokens
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
.. autofunction:: szurubooru_client.tokens.special_token
|
||||
|
||||
|
||||
Negating tokens
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
All query tokens can be negated using either ``.negate`` or by prefixing with the ``-`` operator. Negated tokens mean to *not* match that particular token.
|
||||
|
||||
For example, to match the anonymous tag ``konosuba``, we would do the following:
|
||||
|
||||
```python
|
||||
client.list_posts(query=[anonymous_token("konosuba")])
|
||||
```
|
||||
|
||||
Now to match all posts that *don't* have the ``konosuba`` tag:
|
||||
|
||||
```python
|
||||
client.list_posts(query=[-anonymous_token("konosuba")])
|
||||
# or
|
||||
client.list_posts(query=[anonymous_token("konosuba").negate()])
|
||||
```
|
||||
|
||||
.. _named-tokens-enums:
|
||||
|
||||
Token Enums
|
||||
^^^^^^^^^^^
|
||||
|
||||
-----------------
|
||||
Named Token Enums
|
||||
-----------------
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.TagNamedToken
|
||||
|
||||
.. py:attribute:: Name
|
||||
|
||||
Having given name (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Category
|
||||
|
||||
Having given category (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Created at given date
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Edited at given date
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`LastEditTime <szurubooru_client.tokens.TagNamedToken.LastEditTime>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`LastEditTime <szurubooru_client.tokens.TagNamedToken.LastEditTime>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`LastEditTime <szurubooru_client.tokens.TagNamedToken.LastEditTime>`
|
||||
|
||||
|
||||
.. py:attribute:: Usages
|
||||
|
||||
Used in given number of posts
|
||||
|
||||
|
||||
.. py:attribute:: UsageCount
|
||||
|
||||
Alias of :attr:`Usages <szurubooru_client.tokens.TagNamedToken.Usages>`
|
||||
|
||||
|
||||
.. py:attribute:: PostCount
|
||||
|
||||
Alias of :attr:`Usages <szurubooru_client.tokens.TagNamedToken.Usages>`
|
||||
|
||||
|
||||
.. py:attribute:: SuggestionCount
|
||||
|
||||
With given number of suggestions
|
||||
|
||||
|
||||
.. py:attribute:: ImplicationCount
|
||||
|
||||
With given number of implications
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.PostNamedToken
|
||||
|
||||
.. py:attribute:: Id
|
||||
|
||||
Having given post number
|
||||
|
||||
.. py:attribute:: Tag
|
||||
|
||||
Having given tag (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Score
|
||||
|
||||
Having given score
|
||||
|
||||
|
||||
.. py:attribute:: Uploader
|
||||
|
||||
Uploaded by given user (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Upload
|
||||
|
||||
Alias of :attr:`Uploader <szurubooru_client.tokens.PostNamedToken.Uploader>`
|
||||
|
||||
|
||||
.. py:attribute:: Submit
|
||||
|
||||
Alias of :attr:`Uploader <szurubooru_client.tokens.PostNamedToken.Uploader>`
|
||||
|
||||
|
||||
.. py:attribute:: Comment
|
||||
|
||||
Commented by given user (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Fav
|
||||
|
||||
Favorited by given user (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Pool
|
||||
|
||||
Belonging to the pool with the given id
|
||||
|
||||
|
||||
.. py:attribute:: TagCount
|
||||
|
||||
Having given number of tags
|
||||
|
||||
|
||||
.. py:attribute:: CommentCount
|
||||
|
||||
Having given number of comments
|
||||
|
||||
|
||||
.. py:attribute:: FavCount
|
||||
|
||||
Favorited by given number of users
|
||||
|
||||
|
||||
.. py:attribute:: NoteCount
|
||||
|
||||
Having given number of annotations
|
||||
|
||||
|
||||
.. py:attribute:: NoteText
|
||||
|
||||
Having given note text (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: RelationCount
|
||||
|
||||
Having given number of relations
|
||||
|
||||
|
||||
.. py:attribute:: FeatureCount
|
||||
|
||||
Having been featured given number of times
|
||||
|
||||
|
||||
.. py:attribute:: Type
|
||||
|
||||
``flash`` (or ``swf``) or ``video`` (or ``webm``). Use :attr:`models.posttype` for type-safe values
|
||||
|
||||
|
||||
.. py:attribute:: ContentChecksum
|
||||
|
||||
Having given sha1 checksum
|
||||
|
||||
|
||||
.. py:attribute:: FileSize
|
||||
|
||||
Having given file size (in bytes)
|
||||
|
||||
|
||||
.. py:attribute:: ImageWidth
|
||||
|
||||
Having given image width (where applicable)
|
||||
|
||||
|
||||
.. py:attribute:: ImageHeight
|
||||
|
||||
Having given image height (where applicable)
|
||||
|
||||
|
||||
.. py:attribute:: ImageArea
|
||||
|
||||
Having given number of pixels (image width * image height)
|
||||
|
||||
|
||||
.. py:attribute:: ImageAspectRatio
|
||||
|
||||
Having given aspect ratio (image width / image height)
|
||||
|
||||
|
||||
.. py:attribute:: ImageAr
|
||||
|
||||
Alias of :attr:`ImageAspectRatio <szurubooru_client.tokens.PostNamedToken.ImageAspectRatio>`
|
||||
|
||||
|
||||
.. py:attribute:: Width
|
||||
|
||||
Alias of :attr:`ImageWidth <szurubooru_client.tokens.PostNamedToken.ImageWidth>`
|
||||
|
||||
|
||||
.. py:attribute:: Height
|
||||
|
||||
Alias of :attr:`ImageHeight <szurubooru_client.tokens.PostNamedToken.ImageHeight>`
|
||||
|
||||
|
||||
.. py:attribute:: Ar
|
||||
|
||||
Alias of :attr:`ImageAspectRatio <szurubooru_client.tokens.PostNamedToken.ImageAspectRatio>`
|
||||
|
||||
|
||||
.. py:attribute:: AspectRatio
|
||||
|
||||
Alias of :attr:`ImageAspectRatio <szurubooru_client.tokens.PostNamedToken.ImageAspectRatio>`
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Posted at given date
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostNamedToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: Date
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostNamedToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: Time
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostNamedToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Edited at given date
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostNamedToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostNamedToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostNamedToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: CommentDate
|
||||
|
||||
Commented at given date
|
||||
|
||||
|
||||
.. py:attribute:: CommentTime
|
||||
|
||||
Alias of :attr:`CommentDate <szurubooru_client.tokens.PostNamedToken.CommentDate>`
|
||||
|
||||
|
||||
.. py:attribute:: FavDate
|
||||
|
||||
Last favorited at given time
|
||||
|
||||
|
||||
.. py:attribute:: FavTime
|
||||
|
||||
Alias of :attr:`FavDate <szurubooru_client.tokens.PostNamedToken.FavDate>`
|
||||
|
||||
|
||||
.. py:attribute:: FeatureDate
|
||||
|
||||
Featured at given date
|
||||
|
||||
|
||||
.. py:attribute:: FeatureTime
|
||||
|
||||
Alias of :attr:`FeatureDate <szurubooru_client.tokens.PostNamedToken.FeatureDate>`
|
||||
|
||||
|
||||
.. py:attribute:: Safety
|
||||
|
||||
Use :class:`PostSafety <szurubooru_client.models.PostSafety>` for the type-safe version
|
||||
|
||||
|
||||
.. py:attribute:: Rating
|
||||
|
||||
Alias of :attr:`Safety <szurubooru_client.tokens.PostNamedToken.Safety>`
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.CommentNamedToken
|
||||
|
||||
.. py:attribute:: Id
|
||||
|
||||
Specific comment id
|
||||
|
||||
|
||||
.. py:attribute:: Post
|
||||
|
||||
Specific post id
|
||||
|
||||
|
||||
.. py:attribute:: User
|
||||
|
||||
Created by given user (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: Author
|
||||
|
||||
Alias of user
|
||||
|
||||
|
||||
.. py:attribute:: Text
|
||||
|
||||
Containing given text (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Created at given date
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.CommentNamedToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Whose most recent edit date matches given date
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentNamedToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentNamedToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentNamedToken.LastEditDate>`
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.UserNamedToken
|
||||
|
||||
|
||||
.. py:attribute:: Name
|
||||
|
||||
Having given name (accepts wildcards)
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Registered at given date
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.UserNamedToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastLoginDate
|
||||
|
||||
Whose most recent login date matches given date
|
||||
|
||||
|
||||
.. py:attribute:: LastLoginTime
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserNamedToken.LastLoginDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LoginDate
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserNamedToken.LastLoginDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LoginTime
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserNamedToken.LastLoginDate>`
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.SnapshotNamedToken
|
||||
|
||||
|
||||
.. py:attribute:: Type
|
||||
|
||||
Involving given resource type
|
||||
|
||||
|
||||
.. py:attribute:: Id
|
||||
|
||||
Involving given resource id
|
||||
|
||||
|
||||
.. py:attribute:: Date
|
||||
|
||||
Created at given date
|
||||
|
||||
|
||||
.. py:attribute:: Time
|
||||
|
||||
Alias of :attr:`Date <szurubooru_client.tokens.UserNamedToken.LastLoginDate>`
|
||||
|
||||
|
||||
.. py:attribute:: Operation
|
||||
|
||||
``modified``, ``created``, ``deleted`` or ``merged``. Use :attr:`SnapshotType <szurubooru_client.models.SnapshotType>` for type-safe values
|
||||
|
||||
|
||||
.. py:attribute:: User
|
||||
|
||||
Name of the user that created given snapshot (accepts wildcards)
|
||||
|
||||
.. _sort-tokens-enums:
|
||||
|
||||
----------------
|
||||
Sort Token Enums
|
||||
----------------
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.PostSortToken
|
||||
|
||||
.. py:attribute:: Random
|
||||
|
||||
As random as it can get
|
||||
|
||||
|
||||
.. py:attribute:: Id
|
||||
|
||||
Highest to lowest post number
|
||||
|
||||
|
||||
.. py:attribute:: Score
|
||||
|
||||
Highest scored
|
||||
|
||||
|
||||
.. py:attribute:: TagCount
|
||||
|
||||
With most tags
|
||||
|
||||
|
||||
.. py:attribute:: CommentCount
|
||||
|
||||
Most commented first
|
||||
|
||||
|
||||
.. py:attribute:: FavCount
|
||||
|
||||
Loved by most
|
||||
|
||||
|
||||
.. py:attribute:: NoteCount
|
||||
|
||||
With most annotations
|
||||
|
||||
|
||||
.. py:attribute:: RelationCount
|
||||
|
||||
With most relations
|
||||
|
||||
|
||||
.. py:attribute:: FeatureCount
|
||||
|
||||
Most often featured
|
||||
|
||||
|
||||
.. py:attribute:: FileSize
|
||||
|
||||
Largest files first
|
||||
|
||||
|
||||
.. py:attribute:: ImageWidth
|
||||
|
||||
Widest images first
|
||||
|
||||
|
||||
.. py:attribute:: ImageHeight
|
||||
|
||||
Tallest images first
|
||||
|
||||
|
||||
.. py:attribute:: ImageArea
|
||||
|
||||
Largest images first
|
||||
|
||||
|
||||
.. py:attribute:: Width
|
||||
|
||||
Alias of :attr:`ImageWidth <szurubooru_client.tokens.PostSortToken.imagewidth>`
|
||||
|
||||
|
||||
.. py:attribute:: Height
|
||||
|
||||
Alias of :attr:`ImageHeight <szurubooru_client.tokens.PostSortToken.imageheight>`
|
||||
|
||||
|
||||
.. py:attribute:: Area
|
||||
|
||||
Alias of :attr:`ImageArea <szurubooru_client.tokens.PostSortToken.imagearea>`
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Newest to oldest (pretty much same as id)
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostSortToken.creationdate>`
|
||||
|
||||
|
||||
.. py:attribute:: Date
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostSortToken.creationdate>`
|
||||
|
||||
|
||||
.. py:attribute:: Time
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.PostSortToken.creationdate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Like :attr:`CreationDate <szurubooru_client.tokens.PostSortToken.creationdate>`, only looks at last edit time instead
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostSortToken.lasteditdate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostSortToken.lasteditdate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.PostSortToken.lasteditdate>`
|
||||
|
||||
|
||||
.. py:attribute:: CommentDate
|
||||
|
||||
Recently commented by anyone
|
||||
|
||||
|
||||
.. py:attribute:: CommentTime
|
||||
|
||||
Alias of :attr:`CommentDate <szurubooru_client.tokens.PostSortToken.commentdate>`
|
||||
|
||||
|
||||
.. py:attribute:: FavDate
|
||||
|
||||
Recently added to favorites by anyone
|
||||
|
||||
|
||||
.. py:attribute:: FavTime
|
||||
|
||||
Alias of :attr:`FavDate <szurubooru_client.tokens.PostSortToken.favdate>`
|
||||
|
||||
|
||||
.. py:attribute:: FeatureDate
|
||||
|
||||
Recently featured
|
||||
|
||||
|
||||
.. py:attribute:: FeatureTime
|
||||
|
||||
Alias of :attr:`FeatureDate <szurubooru_client.tokens.PostSortToken.featuredate>`
|
||||
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.TagSortToken
|
||||
|
||||
.. py:attribute:: Random
|
||||
|
||||
As random as it can get
|
||||
|
||||
|
||||
.. py:attribute:: Name
|
||||
|
||||
A to Z
|
||||
|
||||
|
||||
.. py:attribute:: Category
|
||||
|
||||
Category (a to z)
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Recently created first
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.TagSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Recently edited first
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.TagSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.TagSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.TagSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: Usages
|
||||
|
||||
Used in most posts first
|
||||
|
||||
|
||||
.. py:attribute:: UsageCount
|
||||
|
||||
Alias of :attr:`Usages <szurubooru_client.tokens.TagSortToken.Usages>`
|
||||
|
||||
|
||||
.. py:attribute:: PostCount
|
||||
|
||||
Alias of :attr:`Usages <szurubooru_client.tokens.TagSortToken.Usages>`
|
||||
|
||||
|
||||
.. py:attribute:: SuggestionCount
|
||||
|
||||
With most suggestions first
|
||||
|
||||
|
||||
.. py:attribute:: ImplicationCount
|
||||
|
||||
With most implications first
|
||||
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.CommentSortToken
|
||||
|
||||
|
||||
.. py:attribute:: Random
|
||||
|
||||
As random as it can get
|
||||
|
||||
|
||||
.. py:attribute:: User
|
||||
|
||||
Author name, a to z
|
||||
|
||||
|
||||
.. py:attribute:: Author
|
||||
|
||||
Alias of user
|
||||
|
||||
|
||||
.. py:attribute:: Post
|
||||
|
||||
Post id, newest to oldest
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Newest to oldest
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.CommentSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastEditDate
|
||||
|
||||
Recently edited first
|
||||
|
||||
|
||||
.. py:attribute:: LastEditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentSortToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditDate
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentSortToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:attribute:: EditTime
|
||||
|
||||
Alias of :attr:`LastEditDate <szurubooru_client.tokens.CommentSortToken.LastEditDate>`
|
||||
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.UserSortToken
|
||||
|
||||
.. py:attribute:: Random
|
||||
|
||||
As random as it can get
|
||||
|
||||
|
||||
.. py:attribute:: Name
|
||||
|
||||
A to z
|
||||
|
||||
|
||||
.. py:attribute:: CreationDate
|
||||
|
||||
Newest to oldest
|
||||
|
||||
|
||||
.. py:attribute:: CreationTime
|
||||
|
||||
Alias of :attr:`CreationDate <szurubooru_client.tokens.UserSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LastLoginDate
|
||||
|
||||
Recently active first
|
||||
|
||||
|
||||
.. py:attribute:: LastLoginTime
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LoginDate
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserSortToken.CreationDate>`
|
||||
|
||||
|
||||
.. py:attribute:: LoginTime
|
||||
|
||||
Alias of :attr:`LastLoginDate <szurubooru_client.tokens.UserSortToken.CreationDate>`
|
||||
|
||||
.. _special-tokens-enum:
|
||||
|
||||
-------------------
|
||||
Special Token Enums
|
||||
-------------------
|
||||
|
||||
.. py:class:: szurubooru_client.tokens.PostSpecialToken
|
||||
|
||||
|
||||
.. py:attribute:: Liked
|
||||
|
||||
Posts liked by currently logged-in user
|
||||
|
||||
|
||||
.. py:attribute:: Disliked
|
||||
|
||||
Posts disliked by currently logged in user
|
||||
|
||||
|
||||
.. py:attribute:: Fav
|
||||
|
||||
Posts added to favorites by currently logged-in user
|
||||
|
||||
|
||||
.. py:attribute:: Tumbleweed
|
||||
|
||||
Posts with score of 0, without comments and without favorites
|
||||
|
|
@ -1,3 +1,21 @@
|
|||
## v0.5.0 (2024-09-05)
|
||||
|
||||
### Feat
|
||||
|
||||
- **all**: adding python library wrapper
|
||||
|
||||
### Fix
|
||||
|
||||
- **python**: refactoring python client
|
||||
- **cargo**: adding vendored openssl to help with build
|
||||
- **python-client**: fixing paged search results to use a PyList instead
|
||||
|
||||
### Refactor
|
||||
|
||||
- **python**: adding custom python modules to possibly help with documentation
|
||||
- **models**: removing unnecessary python classes
|
||||
- **models**: saving some changes in prep of python library
|
||||
|
||||
## v0.4.0 (2024-08-12)
|
||||
|
||||
### Feat
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "szurubooru-client"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
authors = ["Scott Lyons <scottalyons@gmail.com>"]
|
||||
description = "A wrapper around the Szurubooru API, including type-safe Query and Sort tokens"
|
||||
|
|
@ -16,13 +16,17 @@ chrono = { version = "0.4.38", features = ["serde"] }
|
|||
derive_builder = "0.20.0"
|
||||
futures-util = "0.3.30"
|
||||
hex = "0.4.3"
|
||||
openssl = { version = "0.10.66", features = ["vendored"] }
|
||||
pyo3 = { version="0.22.0", optional=true, features=["chrono-tz", "chrono", "serde", "experimental-async"] }
|
||||
reqwest = { version = "0.12.5", features = ["json", "multipart", "stream"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde-pyobject = { version = "0.4.0", optional = true }
|
||||
serde_json = "1.0.120"
|
||||
sha1 = "0.10.6"
|
||||
strum = { version = "0.26.3", features = ["derive", "strum_macros"] }
|
||||
strum_macros = "0.26.4"
|
||||
thiserror = "1.0.63"
|
||||
tokio = { version = "1.39.2", features = ["rt", "sync"], optional = true }
|
||||
tracing = "0.1.40"
|
||||
url = "2.5.2"
|
||||
urlencoding = "2.1.3"
|
||||
|
|
@ -30,3 +34,11 @@ urlencoding = "2.1.3"
|
|||
[dev-dependencies]
|
||||
mockito = "1.4.0"
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
|
||||
[features]
|
||||
python = ["dep:pyo3", "dep:tokio", "dep:serde-pyobject", "pyo3/extension-module"]
|
||||
extension-module = ["pyo3/extension-module"]
|
||||
|
||||
[lib]
|
||||
name = "szurubooru_client"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
|||
24
szurubooru-client/pyproject.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[build-system]
|
||||
requires = ["maturin>=1.7,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "szurubooru_client"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Environment :: Web Environment",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: POSIX",
|
||||
"Topic :: Multimedia :: Graphics",
|
||||
|
||||
]
|
||||
dynamic = ["version"]
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module", "python"]
|
||||
|
|
@ -128,10 +128,10 @@ impl SzurubooruClient {
|
|||
|
||||
/// Construct a new request using the existing client auth and base URL
|
||||
/// All requests start with the [SzurubooruClient] struct.
|
||||
/// The (request)[SzurubooruClient::request],
|
||||
/// (with_fields)[SzurubooruClient::fields],
|
||||
/// (limit)[SzurubooruClient::limit] and
|
||||
/// (offset)[SzurubooruClient::offset] methods all return a [SzurubooruRequest] struct that will
|
||||
/// The [request](crate::SzurubooruClient::request),
|
||||
/// [with_fields](crate::SzurubooruClient::with_fields),
|
||||
/// [with_limit](crate::SzurubooruClient::with_limit) and
|
||||
/// [with_offset](crate::SzurubooruClient::with_offset) methods all return a [SzurubooruRequest] struct that will
|
||||
/// enable you to actually make the requests.
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
|
|
@ -149,31 +149,36 @@ impl SzurubooruClient {
|
|||
|
||||
/// Construct a new request while selecting only the given fields
|
||||
/// The Szurubooru API supports selecting a subset of fields for a given resource.
|
||||
/// Most resource (models)[szurubooru_client::models] have [Option] fields because of that.
|
||||
/// Most resource [models](crate::models) have [Option] fields because of that.
|
||||
/// The default is to return all fields for a given resource.
|
||||
/// See [here](https://github.com/rr-/szurubooru/blob/master/doc/API.md#field-selecting) for
|
||||
/// more details
|
||||
///
|
||||
/// For example, to select only the `version`, `id` and `content_url` fields of a
|
||||
/// (PostResource)[szurubooru_client::models::PostResource]
|
||||
/// [PostResource]
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
/// # async {
|
||||
/// let client = SzurubooruClient::new_with_token("http://localhost:5001", "myuser", "sz-123456", true).unwrap();
|
||||
/// let new_request = client.request().with_fields(vec!["version", "id", "content_url"]);
|
||||
/// let new_request = client.request().with_fields(vec!["version".to_string(), "id".to_string(), "content_url".to_string()]);
|
||||
/// # };
|
||||
/// # ()
|
||||
/// ```
|
||||
pub fn with_fields<'a>(&'a self, fields: Vec<&'a str>) -> SzurubooruRequest {
|
||||
pub fn with_fields(&self, fields: Vec<String>) -> SzurubooruRequest {
|
||||
self.request().with_fields(fields)
|
||||
}
|
||||
|
||||
/// The same as [with_fields](SzurubooruClient::with_fields), but accepts an Option type instead
|
||||
pub fn with_optional_fields(&self, fields: Option<Vec<String>>) -> SzurubooruRequest {
|
||||
self.request().with_optional_fields(fields)
|
||||
}
|
||||
|
||||
/// Construct a new request with the given limit
|
||||
/// The Szurubooru API supports limiting the number of resources returned for Paginated
|
||||
/// API endpoints.
|
||||
///
|
||||
/// For example, to limit the number of pools returned by (list_pools)[SzurubooruRequest::list_pools]
|
||||
/// For example, to limit the number of pools returned by [list_pools](SzurubooruRequest::list_pools)
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
|
|
@ -190,12 +195,17 @@ impl SzurubooruClient {
|
|||
self.request().with_limit(limit)
|
||||
}
|
||||
|
||||
/// The same as [with_limit](SzurubooruClient::with_limit), but accepts an Option type instead
|
||||
pub fn with_optional_limit(&self, limit: Option<u32>) -> SzurubooruRequest {
|
||||
self.request().with_optional_limit(limit)
|
||||
}
|
||||
|
||||
/// Construct a new request starting at the given offset
|
||||
/// The Szurubooru API supports offsetting the results returned from Paginated API
|
||||
/// endpoints. Use this offset in combination with the limit to page through
|
||||
/// large result sets.
|
||||
///
|
||||
/// For example, to offset the list of pools returned by (list_pools)[SzurubooruRequest::list_pools]
|
||||
/// For example, to offset the list of pools returned by [list_pools](SzurubooruRequest::list_pools)
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
|
|
@ -211,14 +221,23 @@ impl SzurubooruClient {
|
|||
pub fn with_offset(&self, offset: u32) -> SzurubooruRequest {
|
||||
self.request().with_offset(offset)
|
||||
}
|
||||
|
||||
/// The same as [with_offset](SzurubooruClient::with_offset), but accepts an Option type instead
|
||||
pub fn with_optional_offset(&self, offset: Option<u32>) -> SzurubooruRequest {
|
||||
self.request().with_optional_offset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A type that represents a single Szurubooru request.
|
||||
pub struct SzurubooruRequest<'a> {
|
||||
fields: Option<Vec<&'a str>>,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
/// The currently selected fields to return (if applicable)
|
||||
pub fields: Option<Vec<String>>,
|
||||
/// The maximum number of resources to return (if supported by the API endpoint)
|
||||
pub limit: Option<u32>,
|
||||
/// The number of resource to skip before returning any results
|
||||
/// (if supported by the API endpoint)
|
||||
pub offset: Option<u32>,
|
||||
client: &'a SzurubooruClient,
|
||||
}
|
||||
|
||||
|
|
@ -234,32 +253,40 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Select which fields to return from the query.
|
||||
/// The Szurubooru API supports selecting a subset of fields for a given resource.
|
||||
/// Most resource (models)[szurubooru_client::models] have [Option] fields because of that.
|
||||
/// Most resource [models](crate::models) have [Option] fields because of that.
|
||||
/// The default is to return all fields for a given resource.
|
||||
/// See [here](https://github.com/rr-/szurubooru/blob/master/doc/API.md#field-selecting) for
|
||||
/// more details
|
||||
///
|
||||
/// For example, to select only the `version`, `id` and `content_url` fields of a
|
||||
/// (PostResource)[szurubooru_client::models::PostResource]
|
||||
/// [PostResource]
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
/// # async {
|
||||
/// let client = SzurubooruClient::new_with_token("http://localhost:5001", "myuser", "sz-123456", true).unwrap();
|
||||
/// let new_request = client.request().with_fields(vec!["version", "id", "content_url"]);
|
||||
/// let new_request = client.request().with_fields(vec!["version".to_string(), "id".to_string(), "content_url".to_string()]);
|
||||
/// # };
|
||||
/// # ()
|
||||
/// ```
|
||||
pub fn with_fields(mut self, fields: Vec<&'a str>) -> Self {
|
||||
pub fn with_fields(mut self, fields: Vec<String>) -> Self {
|
||||
self.fields = Some(fields);
|
||||
self
|
||||
}
|
||||
|
||||
/// The same as [with_fields](SzurubooruRequest::with_fields), but accepts an Option type instead
|
||||
pub fn with_optional_fields(self, val: Option<Vec<String>>) -> Self {
|
||||
match val {
|
||||
Some(f) => self.with_fields(f),
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Limit the number of returned results
|
||||
/// The Szurubooru API supports limiting the number of resources returned for Paginated
|
||||
/// API endpoints.
|
||||
///
|
||||
/// For example, to limit the number of pools returned by (list_pools)[SzurubooruRequest::list_pools]
|
||||
/// For example, to limit the number of pools returned by [list_pools](SzurubooruRequest::list_pools)
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
|
|
@ -277,12 +304,20 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
/// The same as [with_limit](SzurubooruRequest::with_limit), but accepts an Option type instead
|
||||
pub fn with_optional_limit(self, val: Option<u32>) -> Self {
|
||||
match val {
|
||||
Some(f) => self.with_limit(f),
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip a certain number of records
|
||||
/// The Szurubooru API supports offsetting the results returned from Paginated API
|
||||
/// endpoints. Use this offset in combination with the limit to page through
|
||||
/// large result sets.
|
||||
///
|
||||
/// For example, to offset the list of pools returned by (list_pools)[SzurubooruRequest::list_pools]
|
||||
/// For example, to offset the list of pools returned by [list_pools](SzurubooruRequest::list_pools)
|
||||
/// ```no_run
|
||||
/// # use szurubooru_client::SzurubooruClient;
|
||||
/// # #[allow(unused)]
|
||||
|
|
@ -300,6 +335,14 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
/// The same as [with_offset](SzurubooruRequest::with_offset), but accepts an Option type instead
|
||||
pub fn with_optional_offset(self, val: Option<u32>) -> Self {
|
||||
match val {
|
||||
Some(f) => self.with_offset(f),
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn prep_request<T>(
|
||||
&self,
|
||||
|
|
@ -381,12 +424,14 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
async fn handle_response(&self, response: Response) -> SzurubooruResult<Response> {
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
let status = response.status();
|
||||
let resp_json = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(SzurubooruClientError::RequestError)?;
|
||||
|
||||
let server_error = serde_json::from_str::<SzurubooruServerError>(&resp_json)
|
||||
.map_err(|e| SzurubooruClientError::ResponseParsingError(e, resp_json))?;
|
||||
.map_err(|_e| SzurubooruClientError::ResponseError(status, resp_json))?;
|
||||
Err(SzurubooruClientError::SzurubooruServerError(server_error))
|
||||
} else {
|
||||
Ok(response)
|
||||
|
|
@ -406,13 +451,12 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
let response = self
|
||||
.handle_response(response.map_err(SzurubooruClientError::RequestError)?)
|
||||
.await?;
|
||||
//.error_for_status()
|
||||
//.map_err(SzurubooruClientError::RequestError)?;
|
||||
|
||||
let response_text = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(SzurubooruClientError::RequestError)?;
|
||||
|
||||
serde_json::from_str::<SzuruEither<T, SzurubooruServerError>>(&response_text)
|
||||
.map_err(|e| SzurubooruClientError::ResponseParsingError(e, response_text))?
|
||||
.into_result()
|
||||
|
|
@ -447,7 +491,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Updates an existing tag category using specified parameters. Name must match
|
||||
/// `tag_category_name_regex` from server's configuration. All fields except
|
||||
/// [version](models::TagCategoryResource::version) are optional - update concerns only provided fields.
|
||||
/// [version](crate::models::TagCategoryResource::version) are optional - update concerns only provided fields.
|
||||
pub async fn update_tag_category<T>(
|
||||
&self,
|
||||
name: T,
|
||||
|
|
@ -495,8 +539,9 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Searches for tags.
|
||||
/// See the (named tokens)[tokens::TagNamedToken] and (sort tokens)[tokens::TagSortToken] for
|
||||
/// all possible query tokens, or use (QueryToken)[tokens::QueryToken] for a custom token
|
||||
/// See the [named tokens](crate::tokens::TagNamedToken) and
|
||||
/// [sort tokens](crate::tokens::TagSortToken) for all possible query tokens, or use
|
||||
/// [QueryToken] for a custom token
|
||||
pub async fn list_tags(
|
||||
&self,
|
||||
query: Option<&Vec<QueryToken>>,
|
||||
|
|
@ -507,7 +552,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Creates a new tag using specified parameters. Names, suggestions and implications must
|
||||
/// match `tag_name_regex` from server's configuration. Category must exist and is the same
|
||||
/// as the `name` field within (TagCategoryResource)[models::TagCategoryResource] resource.
|
||||
/// as the `name` field within [TagCategoryResource] resource.
|
||||
/// Suggestions and implications are optional. If specified implied tags or suggested tags do
|
||||
/// not exist yet, they will be automatically created. Tags created automatically have no
|
||||
/// implications, no suggestions, one name and their category is set to the first tag category
|
||||
|
|
@ -519,7 +564,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Updates an existing tag using specified parameters. Names, suggestions and implications must
|
||||
/// match `tag_name_regex` from server's configuration. Category must exist and is the same
|
||||
/// as the `name` field within (TagCategoryResource)[models::TagCategoryResource] resource.
|
||||
/// as the `name` field within [TagCategoryResource] resource.
|
||||
/// Suggestions and implications are optional. If specified implied tags or suggested tags do
|
||||
/// not exist yet, they will be automatically created. Tags created automatically have no
|
||||
/// implications, no suggestions, one name and their category is set to the first tag category
|
||||
|
|
@ -562,13 +607,13 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// Removes source tag and merges all of its usages, suggestions and implications to the
|
||||
/// target tag. Other tag properties such as category and aliases do not get transferred
|
||||
/// and are discarded.
|
||||
pub async fn merge_tag(&self, merge_opts: &MergeTags) -> SzurubooruResult<TagResource> {
|
||||
pub async fn merge_tags(&self, merge_opts: &MergeTags) -> SzurubooruResult<TagResource> {
|
||||
self.do_request(Method::POST, "/api/tag-merge", None, Some(merge_opts))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Lists siblings of given tag, e.g. tags that were used in the same posts as the given tag.
|
||||
/// The (occurrences)[models::TagSibling::occurrences] field signifies how many times a given
|
||||
/// The [occurrences](crate::models::TagSibling::occurrences) field signifies how many times a given
|
||||
/// sibling appears with given tag. Results are sorted by occurrences count and the list is
|
||||
/// truncated to the first 50 elements. Doesn't use paging.
|
||||
pub async fn get_tag_siblings<T>(
|
||||
|
|
@ -584,9 +629,8 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Searches for posts.
|
||||
/// See (PostNamedToken)[tokens::PostNamedToken], (PostSortToken)[tokens::PostSortToken] and
|
||||
/// (PostSpecialToken)[tokens::PostSpecialToken] for valid tokens to use with this method, or
|
||||
/// use (QueryToken)[tokens::QueryToken] to construct a custom token
|
||||
/// See [PostNamedToken], [PostSortToken] and [PostSpecialToken] for valid tokens to use with
|
||||
/// this method, or use [QueryToken] to construct a custom token
|
||||
pub async fn list_posts(
|
||||
&self,
|
||||
query: Option<&Vec<QueryToken>>,
|
||||
|
|
@ -602,6 +646,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
method: Method,
|
||||
cupost: &CreateUpdatePost,
|
||||
) -> SzurubooruResult<PostResource> {
|
||||
if method == Method::POST && cupost.safety.is_none() {
|
||||
return Err(SzurubooruClientError::ValidationError(
|
||||
"Safety must be set".to_string(),
|
||||
));
|
||||
}
|
||||
self.do_request(method, path, None, Some(cupost)).await
|
||||
}
|
||||
|
||||
|
|
@ -609,7 +658,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// the image.
|
||||
/// If specified tags do not exist yet, they will be automatically created. Tags created
|
||||
/// automatically have no implications, no suggestions, one name and their category is set to
|
||||
/// the first tag category found. (safety)[models::CreateUpdatePost::safety] must be any of
|
||||
/// the first tag category found. [safety](crate::models::CreateUpdatePost::safety) must be any of
|
||||
/// `safe`, `sketchy` or `unsafe`.
|
||||
/// Relations must contain valid post IDs. If `flag` is omitted, they will be defined by
|
||||
/// default (`"loop"` will be set for all video posts, and `"sound"` will be auto-detected).
|
||||
|
|
@ -627,7 +676,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Update an existing post
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// (CreateUpdatePost)[models::CreateUpdatePost]
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn update_post(
|
||||
&self,
|
||||
post_id: u32,
|
||||
|
|
@ -639,6 +688,23 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|pr| self.propagate_urls(pr))
|
||||
}
|
||||
|
||||
/// Update an existing post from a given URL
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn update_post_from_url(
|
||||
&self,
|
||||
post_id: u32,
|
||||
update_post: &CreateUpdatePost,
|
||||
) -> SzurubooruResult<PostResource> {
|
||||
assert!(update_post.content_url.is_some());
|
||||
let path = format!("/api/post/{post_id}");
|
||||
self.create_update_post_from_url(&path, Method::PUT, update_post)
|
||||
.await
|
||||
.map(|pr| self.propagate_urls(pr))
|
||||
}
|
||||
|
||||
// Create function to upload by byte array in the future
|
||||
|
||||
fn part_from_file(&self, file: &mut File) -> SzurubooruResult<Part> {
|
||||
let mut bytes = vec![];
|
||||
file.read_to_end(&mut bytes)
|
||||
|
|
@ -686,7 +752,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Create a new post from a file handle
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// (CreateUpdatePost)[models::CreateUpdatePost]
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn create_post_from_file<T>(
|
||||
&self,
|
||||
file: &mut File,
|
||||
|
|
@ -711,7 +777,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Create a new post from a file path
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// (CreateUpdatePost)[models::CreateUpdatePost]
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn create_post_from_file_path(
|
||||
&self,
|
||||
file_path: impl AsRef<Path>,
|
||||
|
|
@ -731,7 +797,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Create a post from a token previously generated by
|
||||
/// (upload_temporary_file_from_path)[SzurubooruRequest::upload_temporary_file_from_path]
|
||||
/// [upload_temporary_file_from_path](SzurubooruRequest::upload_temporary_file_from_path)
|
||||
pub async fn create_post_from_token(
|
||||
&self,
|
||||
new_post: &CreateUpdatePost,
|
||||
|
|
@ -752,7 +818,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Update an existing post from an open File handle
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// (CreateUpdatePost)[models::CreateUpdatePost]
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn update_post_from_file(
|
||||
&self,
|
||||
post_id: u32,
|
||||
|
|
@ -776,7 +842,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Update an existing post from a file path
|
||||
/// See [SzurubooruRequest::create_post_from_url] for more details about the fields in
|
||||
/// (CreateUpdatePost)[models::CreateUpdatePost]
|
||||
/// [CreateUpdatePost]
|
||||
pub async fn update_post_from_file_path(
|
||||
&self,
|
||||
post_id: u32,
|
||||
|
|
@ -825,6 +891,27 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|pr| self.propagate_urls(pr))
|
||||
}
|
||||
|
||||
/// Update a post from a token previously generated by
|
||||
/// [upload_temporary_file_from_path](SzurubooruRequest::upload_temporary_file_from_path)
|
||||
pub async fn update_post_from_token(
|
||||
&self,
|
||||
post_id: u32,
|
||||
update_post: &CreateUpdatePost,
|
||||
) -> SzurubooruResult<PostResource> {
|
||||
assert!(update_post.content_token.is_some());
|
||||
let url = format!("/api/post/{post_id}");
|
||||
self.create_update_post_from_file(
|
||||
None,
|
||||
None,
|
||||
None::<String>,
|
||||
&url,
|
||||
Method::PUT,
|
||||
update_post,
|
||||
)
|
||||
.await
|
||||
.map(|pr| self.propagate_urls(pr))
|
||||
}
|
||||
|
||||
async fn get_post_content(
|
||||
&self,
|
||||
post_id: u32,
|
||||
|
|
@ -874,7 +961,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|cr| cr.bytes_stream())
|
||||
}
|
||||
|
||||
///Fetches the given post ID's image as a (Bytes)[bytes::Bytes] struct
|
||||
///Fetches the given post ID's image as a [Bytes](bytes::Bytes) struct
|
||||
pub async fn get_image_bytes(&self, post_id: u32) -> SzurubooruResult<bytes::Bytes> {
|
||||
let content_response = self.get_post_content(post_id, false).await?;
|
||||
|
||||
|
|
@ -884,7 +971,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map_err(SzurubooruClientError::RequestError)
|
||||
}
|
||||
|
||||
///Fetches the given post ID's thumbnail as a (Bytes)[bytes::Bytes] struct
|
||||
///Fetches the given post ID's thumbnail as a [Bytes](bytes::Bytes) struct
|
||||
pub async fn get_thumbnail_bytes(&self, post_id: u32) -> SzurubooruResult<bytes::Bytes> {
|
||||
let content_response = self.get_post_content(post_id, true).await?;
|
||||
|
||||
|
|
@ -934,7 +1021,12 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
path: impl AsRef<Path>,
|
||||
) -> SzurubooruResult<()> {
|
||||
let mut stream = self.get_image_bytestream(post_id).await?;
|
||||
let mut file = File::open(path.as_ref()).map_err(SzurubooruClientError::IOError)?;
|
||||
let mut file = File::options()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(path.as_ref())
|
||||
.map_err(SzurubooruClientError::IOError)?;
|
||||
self.write_content_to_file(&mut file, &mut stream).await
|
||||
}
|
||||
|
||||
|
|
@ -989,32 +1081,34 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|isr| self.propagate_urls(isr))
|
||||
}
|
||||
|
||||
// Need to add a reverse search for bytes
|
||||
|
||||
/// Searches for an exact match of a file based on the SHA1 checksum
|
||||
pub async fn posts_for_file(
|
||||
pub async fn post_for_file(
|
||||
&self,
|
||||
mut file: &mut File,
|
||||
) -> SzurubooruResult<PagedSearchResult<PostResource>> {
|
||||
) -> SzurubooruResult<Option<PostResource>> {
|
||||
let mut hasher = Sha1::new();
|
||||
std::io::copy(&mut file, &mut hasher).map_err(SzurubooruClientError::IOError)?;
|
||||
let hash = hasher.finalize();
|
||||
let hex_string = hex::encode(hash);
|
||||
|
||||
let qt = QueryToken::token(PostNamedToken::ContentChecksum, hex_string);
|
||||
self.list_posts(Some(&vec![qt]))
|
||||
let psr = self
|
||||
.list_posts(Some(&vec![qt]))
|
||||
.await
|
||||
.map(|psr| self.propagate_urls(psr))
|
||||
.map(|psr| self.propagate_urls(psr))?;
|
||||
Ok(psr.results.first().cloned())
|
||||
}
|
||||
|
||||
/// Searches for an exact match of a file path based on the SHA1 checksum
|
||||
pub async fn posts_for_file_path(
|
||||
pub async fn post_for_file_path(
|
||||
&self,
|
||||
file_path: impl AsRef<Path>,
|
||||
) -> SzurubooruResult<PagedSearchResult<PostResource>> {
|
||||
) -> SzurubooruResult<Option<PostResource>> {
|
||||
let mut file = File::open(file_path).map_err(SzurubooruClientError::IOError)?;
|
||||
|
||||
self.posts_for_file(&mut file)
|
||||
.await
|
||||
.map(|psr| self.propagate_urls(psr))
|
||||
self.post_for_file(&mut file).await
|
||||
}
|
||||
|
||||
/// Retrieves information about an existing post.
|
||||
|
|
@ -1043,7 +1137,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
///
|
||||
/// Removes source post and merges all of its tags, relations, scores, favorites and comments to
|
||||
/// the target post. If [MergePost::replace_content] is set to `true`, content of the target post
|
||||
/// the target post. If [MergePost::replace_post_content] is set to `true`, content of the target post
|
||||
/// is replaced using the content of the source post; otherwise it remains unchanged. Source
|
||||
/// post properties such as its safety, source, whether to loop the video and other scalar
|
||||
/// values do not get transferred and are discarded.
|
||||
|
|
@ -1056,6 +1150,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Updates score of authenticated user for given post. Valid scores are -1, 0 and 1.
|
||||
pub async fn rate_post(&self, post_id: u32, score: i8) -> SzurubooruResult<PostResource> {
|
||||
if !(-1..=1).contains(&score) {
|
||||
return Err(SzurubooruClientError::ValidationError(
|
||||
"Score must be -1, 0 or 1".to_string(),
|
||||
));
|
||||
}
|
||||
let rating_obj = RateResource { score };
|
||||
let path = format!("/api/post/{post_id}/score");
|
||||
self.do_request(Method::PUT, &path, None, Some(&rating_obj))
|
||||
|
|
@ -1080,9 +1179,9 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Retrieves the post that is currently featured on the main page in web client. If no post is
|
||||
/// featured, <post> is [Option::None]. Note that this method exists mostly for compatibility
|
||||
/// with setting featured post - most of the time, you'd want to use query global info which
|
||||
/// contains more information.
|
||||
/// featured, the result will be [Option::None]. Note that this method exists mostly for
|
||||
/// compatibility with setting featured post - most of the time, you'd want to use query global
|
||||
/// info which contains more information.
|
||||
pub async fn get_featured_post(&self) -> SzurubooruResult<Option<PostResource>> {
|
||||
self.do_request(Method::GET, "/api/featured-post", None, None::<&String>)
|
||||
.await
|
||||
|
|
@ -1118,7 +1217,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Updates an existing tag category using specified parameters. Name must match
|
||||
/// `tag_category_name_regex` from server's configuration. All fields except the
|
||||
/// [version](models::CreateUpdatePoolCategory::version) field are optional - update concerns
|
||||
/// [version](crate::models::CreateUpdatePoolCategory::version) field are optional - update concerns
|
||||
/// only the provided fields.
|
||||
pub async fn update_pool_category<T>(
|
||||
&self,
|
||||
|
|
@ -1177,7 +1276,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Searches for pools.
|
||||
/// Anonymous tokens are the same as the [name](tokens::PoolNamedToken::Name) token
|
||||
/// Anonymous tokens are the same as the [name](crate::tokens::PoolNamedToken::Name) token
|
||||
pub async fn list_pools(
|
||||
&self,
|
||||
query: Option<&Vec<QueryToken>>,
|
||||
|
|
@ -1189,8 +1288,8 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
|
||||
/// Creates a new pool using specified parameters. Names, suggestions and implications must
|
||||
/// match `pool_name_regex` from server's configuration. Category must exist and is the same as
|
||||
/// [name](models::PoolCategoryResource::name) field.
|
||||
/// [posts](models::CreateUpdatePool::posts) is an optional list of integer post IDs. If the
|
||||
/// [name](crate::models::PoolCategoryResource::name) field.
|
||||
/// [posts](crate::models::CreateUpdatePool::posts) is an optional list of integer post IDs. If the
|
||||
/// specified posts do not exist, an error will be thrown.
|
||||
pub async fn create_pool(
|
||||
&self,
|
||||
|
|
@ -1201,14 +1300,14 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Updates an existing pool using specified parameters. [name](models::CreateUpdatePool::name),
|
||||
/// Updates an existing pool using specified parameters. [names](crate::models::CreateUpdatePool::names),
|
||||
/// must match `pool_name_regex` from server's configuration.
|
||||
/// [category](models::CreateUpdatePool::category) must exist and is the same as
|
||||
/// [name](models::PoolCategoryResource::name) field. [posts](models::CreateUpdatePool::posts)
|
||||
/// [category](crate::models::CreateUpdatePool::category) must exist and is the same as
|
||||
/// [name](crate::models::PoolCategoryResource::name) field. [posts](crate::models::CreateUpdatePool::posts)
|
||||
/// is an optional list of integer post IDs. If the specified posts do not exist yet, an error
|
||||
/// will be thrown. The full list of post IDs must be provided if they are being updated, and
|
||||
/// the previous list of posts will be replaced with the new one. All fields except
|
||||
/// [version](models::CreateUpdatePool::version) are optional - update concerns only provided
|
||||
/// [version](crate::models::CreateUpdatePool::version) are optional - update concerns only provided
|
||||
/// fields.
|
||||
pub async fn update_pool(
|
||||
&self,
|
||||
|
|
@ -1248,7 +1347,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Searches for comments.
|
||||
/// Anonymous tokens are the same as the [text](tokens::CommentNamedToken::text) token
|
||||
/// Anonymous tokens are the same as the [text](crate::tokens::CommentNamedToken::Text) token
|
||||
pub async fn list_comments(
|
||||
&self,
|
||||
query: Option<&Vec<QueryToken>>,
|
||||
|
|
@ -1299,6 +1398,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
comment_id: u32,
|
||||
score: i8,
|
||||
) -> SzurubooruResult<CommentResource> {
|
||||
if !(-1..=1).contains(&score) {
|
||||
return Err(SzurubooruClientError::ValidationError(
|
||||
"Score must be -1, 0 or 1".to_string(),
|
||||
));
|
||||
}
|
||||
let path = format!("/api/comment/{comment_id}/score");
|
||||
let rating = RateResource { score };
|
||||
self.do_request(Method::PUT, &path, None, Some(&rating))
|
||||
|
|
@ -1306,9 +1410,8 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Searches for users
|
||||
/// Anonymous tokens are the same as the [name](tokens::UserNamedToken)
|
||||
/// See [UserNamedToken](tokens::UserNamedToken) and [UserSortToken](tokens::UserSortToken)
|
||||
/// for type-safe tokens
|
||||
/// Anonymous tokens are the same as the [name](crate::tokens::UserNamedToken::Name) token
|
||||
/// See [UserNamedToken] and [UserSortToken] for type-safe tokens
|
||||
pub async fn list_users(
|
||||
&self,
|
||||
query: Option<&Vec<QueryToken>>,
|
||||
|
|
@ -1351,7 +1454,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// Creates a new user using specified parameters. Names and passwords must match
|
||||
/// `user_name_regex` and `password_regex` from server's configuration, respectively.
|
||||
/// Email address, rank and avatar fields are optional. Avatar style can be either
|
||||
/// [gravatar](models::UserAvatarStyle::Gravatar) or [manual](models::UserAvatarStyle::Manual).
|
||||
/// [gravatar](crate::models::UserAvatarStyle::Gravatar) or [manual](crate::models::UserAvatarStyle::Manual).
|
||||
/// `manual` avatar style requires client to pass also the `avatar` file.
|
||||
/// If the rank is empty and the user happens to be the first user ever created,
|
||||
/// become an administrator, whereas subsequent users will be given the rank indicated by
|
||||
|
|
@ -1362,7 +1465,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Create a [UserResource](models::UserResource) with the included Avatar file
|
||||
/// Create a [UserResource] with the included Avatar file
|
||||
/// See [create_user](SzurubooruRequest::create_user) for other applicable fields and
|
||||
/// restrictions
|
||||
pub async fn create_user_with_avatar_file(
|
||||
|
|
@ -1382,7 +1485,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Create a [UserResource](models::UserResource) with the included Avatar file path
|
||||
/// Create a [UserResource] with the included Avatar file path
|
||||
/// See [create_user](SzurubooruRequest::create_user) for other applicable fields and
|
||||
/// restrictions
|
||||
pub async fn create_user_with_avatar_path(
|
||||
|
|
@ -1406,9 +1509,9 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// Updates user using specified parameters. Names and passwords must match
|
||||
/// `user_name_regex` and `password_regex` from server's configuration, respectively.
|
||||
/// Email address, rank and avatar fields are optional. Avatar style can be either
|
||||
/// [gravatar](models::UserAvatarStyle::Gravatar) or [manual](models::UserAvatarStyle::Manual).
|
||||
/// [gravatar](crate::models::UserAvatarStyle::Gravatar) or [manual](crate::models::UserAvatarStyle::Manual).
|
||||
/// `manual` avatar style requires client to pass also the `avatar` file.
|
||||
/// All fields except the [version](models::CreateUpdateUser::version) are optional
|
||||
/// All fields except the [version](crate::models::CreateUpdateUser::version) are optional
|
||||
/// - update concerns only provided fields.
|
||||
pub async fn update_user<T>(
|
||||
&self,
|
||||
|
|
@ -1424,7 +1527,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Update a [UserResource](models::UserResource) with the included Avatar file
|
||||
/// Update a [UserResource] with the included Avatar file
|
||||
/// See [update_user](SzurubooruRequest::update_user) for other applicable fields and
|
||||
/// restrictions
|
||||
pub async fn update_user_with_avatar_file<T>(
|
||||
|
|
@ -1449,7 +1552,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Update a [UserResource](models::UserResource) with the included Avatar file path
|
||||
/// Update a [UserResource] with the included Avatar file path
|
||||
/// See [update_user](SzurubooruRequest::update_user) for other applicable fields and
|
||||
/// restrictions
|
||||
pub async fn update_user_with_avatar_path<T>(
|
||||
|
|
@ -1516,20 +1619,20 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
/// instead of a password.
|
||||
pub async fn create_user_token<T>(
|
||||
&self,
|
||||
name: T,
|
||||
user_name: T,
|
||||
create_token: &CreateUpdateUserAuthToken,
|
||||
) -> SzurubooruResult<UserAuthTokenResource>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let path = format!("/api/user-token/{name}");
|
||||
let path = format!("/api/user-token/{user_name}");
|
||||
self.do_request(Method::POST, &path, None, Some(create_token))
|
||||
.await
|
||||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Updates an existing user token using specified parameters. All fields except the
|
||||
/// [version](models::CreateUpdateUserAuthToken::version) are optional - update concerns only
|
||||
/// [version](crate::models::CreateUpdateUserAuthToken::version) are optional - update concerns only
|
||||
/// provided fields.
|
||||
pub async fn update_user_token<T>(
|
||||
&self,
|
||||
|
|
@ -1547,7 +1650,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Deletes an existing user token using specified parameters. All fields except the
|
||||
/// [version](models::CreateUpdateUserAuthToken::version) are optional - update concerns only
|
||||
/// [version](crate::models::CreateUpdateUserAuthToken::version) are optional - update concerns only
|
||||
/// provided fields.
|
||||
pub async fn delete_user_token<T>(
|
||||
&self,
|
||||
|
|
@ -1600,7 +1703,7 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
}
|
||||
|
||||
/// Lists recent resource snapshots.
|
||||
/// See [SnapshotNamedToken](tokens::SnapshotNamedToken) for query tokens.
|
||||
/// See [SnapshotNamedToken] for query tokens.
|
||||
/// There are no sort tokens. The snapshots are always sorted by creation time.
|
||||
pub async fn list_snapshots(
|
||||
&self,
|
||||
|
|
@ -1611,11 +1714,11 @@ impl<'a> SzurubooruRequest<'a> {
|
|||
.map(|r| self.propagate_urls(r))
|
||||
}
|
||||
|
||||
/// Retrieves simple statistics. [featured_post](models::GlobalInfo::featured_post) is
|
||||
/// [None](Option::None) if there is no featured post yet.
|
||||
/// [server_time](models::GlobalInfo::server_time) is pretty much the same as the Date HTTP
|
||||
/// Retrieves simple statistics. [featured_post](crate::models::GlobalInfo::featured_post) is
|
||||
/// [None] if there is no featured post yet.
|
||||
/// [server_time](crate::models::GlobalInfo::server_time) is pretty much the same as the Date HTTP
|
||||
/// field, only formatted in a manner consistent with other dates. Values in config key are
|
||||
/// taken directly from the server config, with the exception of privilege array keys being
|
||||
/// taken directly from the server config, except for the privilege array keys being
|
||||
/// converted to lower camel case to match the API convention.
|
||||
pub async fn get_global_info(&self) -> SzurubooruResult<GlobalInfo> {
|
||||
self.do_request(Method::GET, "/api/info", None, None::<&String>)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@
|
|||
|
||||
use crate::models::SzuruEither;
|
||||
use base64::EncodeSliceError;
|
||||
use derive_builder::UninitializedFieldError;
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::{create_exception, exceptions::PyException, prelude::*};
|
||||
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::AsRefStr;
|
||||
use thiserror::Error;
|
||||
use url::ParseError as UParseError;
|
||||
|
||||
|
|
@ -14,7 +20,7 @@ pub trait IntoClientResult<T> {
|
|||
fn into_result(self) -> SzurubooruResult<T>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, AsRefStr)]
|
||||
/// Type that represents the various error states that can occur when interacting with
|
||||
/// Szurubooru
|
||||
pub enum SzurubooruClientError {
|
||||
|
|
@ -35,6 +41,9 @@ pub enum SzurubooruClientError {
|
|||
/// Error occurred pas part of the request to the server
|
||||
#[error("Request error {0}")]
|
||||
RequestError(#[source] reqwest::Error),
|
||||
/// Error response with a text response from the server
|
||||
#[error("Response error {0}: Server reply: {1}")]
|
||||
ResponseError(StatusCode, String),
|
||||
/// Error parsing the JSON response from the server
|
||||
#[error("Response Parsing error: {0}: {1}")]
|
||||
ResponseParsingError(
|
||||
|
|
@ -47,6 +56,9 @@ pub enum SzurubooruClientError {
|
|||
/// Error serializing an object as JSON
|
||||
#[error("JSON Serialization error: {0}")]
|
||||
JSONSerializationError(#[source] serde_json::Error),
|
||||
/// Error when validation fails for one of the Builder types
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
/// Error occurred when reading a file
|
||||
#[error("IO Error: {0}")]
|
||||
IOError(#[source] std::io::Error),
|
||||
|
|
@ -61,6 +73,27 @@ impl From<SzurubooruServerError> for SzurubooruClientError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<UninitializedFieldError> for SzurubooruClientError {
|
||||
fn from(value: UninitializedFieldError) -> Self {
|
||||
SzurubooruClientError::ValidationError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
create_exception!(
|
||||
szurubooru_client,
|
||||
SzuruClientError,
|
||||
PyException,
|
||||
"An exception that contains two pieces of information: The error kind and error details"
|
||||
);
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
impl std::convert::From<SzurubooruClientError> for PyErr {
|
||||
fn from(value: SzurubooruClientError) -> Self {
|
||||
SzuruClientError::new_err((value.as_ref().to_string(), value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Type used to represent success or a failure of some kind
|
||||
pub type SzurubooruResult<T> = Result<T, SzurubooruClientError>;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
//! ```
|
||||
//!
|
||||
//! For all other methods for making the requests, see the documentation.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rustdoc::missing_crate_level_docs)]
|
||||
|
||||
|
|
@ -34,5 +33,65 @@ pub use client::SzurubooruRequest;
|
|||
pub mod errors;
|
||||
pub use errors::SzurubooruResult;
|
||||
pub mod models;
|
||||
|
||||
pub mod tokens;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[doc(hidden)]
|
||||
pub mod py;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(feature = "python", pymodule)]
|
||||
/// A Python wrapper around SzurubooruClient
|
||||
mod szurubooru_client {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule_export]
|
||||
pub use crate::{
|
||||
errors::SzuruClientError,
|
||||
/*models::{
|
||||
AroundPostResult, CommentResource, GlobalInfo, ImageSearchResult,
|
||||
ImageSearchSimilarPost, MicroPoolResource, MicroPostResource, MicroTagResource,
|
||||
MicroUserResource, NoteResource, PoolCategoryResource, PoolResource, PostResource,
|
||||
PostSafety, PostType, SnapshotCreationDeletionData, SnapshotData,
|
||||
SnapshotModificationData, SnapshotOperationType, SnapshotResource,
|
||||
SnapshotResourceType, TagCategoryResource, TagResource, TagSibling,
|
||||
UserAuthTokenResource, UserAvatarStyle, UserRank, UserResource,
|
||||
},
|
||||
tokens::{
|
||||
anonymous_token, named_token, sort_token, special_token, CommentNamedToken,
|
||||
CommentSortToken, PoolNamedToken, PoolSortToken, PostNamedToken, PostSortToken,
|
||||
PostSpecialToken, QueryToken, SnapshotNamedToken, TagNamedToken, TagSortToken,
|
||||
UserNamedToken, UserSortToken,
|
||||
},*/
|
||||
py::asynchronous::PythonAsyncClient, py::synchronous::PythonSyncClient,
|
||||
py::PyPagedSearchResult,
|
||||
};
|
||||
|
||||
#[pymodule(name = "_tokens")]
|
||||
mod tokens {
|
||||
#[pymodule_export]
|
||||
pub use crate::tokens::{
|
||||
anonymous_token, named_token, sort_token, special_token, CommentNamedToken,
|
||||
CommentSortToken, PoolNamedToken, PoolSortToken, PostNamedToken, PostSortToken,
|
||||
PostSpecialToken, QueryToken, SnapshotNamedToken, TagNamedToken, TagSortToken,
|
||||
UserNamedToken, UserSortToken,
|
||||
};
|
||||
}
|
||||
|
||||
#[pymodule(name = "_models")]
|
||||
mod models {
|
||||
#[pymodule_export]
|
||||
pub use crate::models::{
|
||||
AroundPostResult, CommentResource, GlobalInfo, ImageSearchResult,
|
||||
ImageSearchSimilarPost, MicroPoolResource, MicroPostResource, MicroTagResource,
|
||||
MicroUserResource, NoteResource, PoolCategoryResource, PoolResource, PostResource,
|
||||
PostSafety, PostType, SnapshotCreationDeletionData, SnapshotData,
|
||||
SnapshotModificationData, SnapshotOperationType, SnapshotResource,
|
||||
SnapshotResourceType, TagCategoryResource, TagResource, TagSibling,
|
||||
UserAuthTokenResource, UserAvatarStyle, UserRank, UserResource,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,18 @@
|
|||
//! See [here](https://github.com/rr-/szurubooru/blob/master/doc/API.md#field-selecting) for
|
||||
//! more information.
|
||||
|
||||
use crate::errors::SzurubooruClientError;
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_builder::Builder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use strum_macros::AsRefStr;
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
#[cfg(feature = "python")]
|
||||
use serde_pyobject::to_pyobject;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
/// Enum used to represent something that's either `Left` or `Right`
|
||||
|
|
@ -39,16 +45,16 @@ impl<T: WithBaseURL> WithBaseURL for UnpagedSearchResult<T> {
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
/// A result of search operation that involves paging
|
||||
///
|
||||
/// Use (offset)[crate::SzurubooruRequest::offset] and (limit)[crate::SzurubooruRequest::limit]
|
||||
/// Use [offset](crate::SzurubooruRequest::with_offset) and [limit](crate::SzurubooruRequest::with_limit)
|
||||
/// to fetch the next page
|
||||
pub struct PagedSearchResult<T> {
|
||||
/// The original query for the request
|
||||
pub query: String,
|
||||
/// The number of [T] to skip forward
|
||||
/// The number of `T` to skip forward
|
||||
pub offset: u32,
|
||||
/// The maximum number of [T] to return
|
||||
/// The maximum number of `T` to return
|
||||
pub limit: u32,
|
||||
/// The total number of [T] that match the [query](PagedSearchResult::query)
|
||||
/// The total number of `T` that match the [query](PagedSearchResult::query)
|
||||
pub total: u32,
|
||||
/// The results themselves
|
||||
pub results: Vec<T>,
|
||||
|
|
@ -82,6 +88,10 @@ impl<T: WithBaseURL> WithBaseURL for Vec<T> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, eq, module = "szurubooru_client.models")
|
||||
)]
|
||||
/// A [tag resource](TagResource) stripped down to `names`, `category` and `usages` fields.
|
||||
pub struct MicroTagResource {
|
||||
/// The tag names and aliases
|
||||
|
|
@ -92,6 +102,16 @@ pub struct MicroTagResource {
|
|||
pub usages: u32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl MicroTagResource {
|
||||
/// Function that generates the representation string for this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// To prevent problems with concurrent resource modification, Szurubooru implements optimistic
|
||||
/// locks using resource versions. Each modifiable resource has its version returned to the client
|
||||
|
|
@ -116,8 +136,12 @@ pub struct ResourceVersion {
|
|||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
/// A single tag. Tags are used to let users search for posts.
|
||||
pub struct TagResource {
|
||||
/// resource version. See [versioning](ResourceVersion)
|
||||
|
|
@ -144,9 +168,19 @@ pub struct TagResource {
|
|||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl TagResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates or updates a tag using specified parameters. Names, suggestions and implications must
|
||||
/// match `tag_name_regex` from server's configuration. Category must exist and is the same as name
|
||||
/// field within <tag-category> resource. Suggestions and implications are optional. If specified
|
||||
/// field within [TagCategoryResource] resource. Suggestions and implications are optional. If specified
|
||||
/// implied tags or suggested tags do not exist yet, they will be automatically created. Tags
|
||||
/// created automatically have no implications, no suggestions, one name and their category is set
|
||||
/// to the first tag category found. If there are no tag categories established yet, an error
|
||||
|
|
@ -161,7 +195,8 @@ pub struct TagResource {
|
|||
/// .expect("A new tag");
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)]
|
||||
#[builder(setter(strip_option))]
|
||||
//#[builder(pattern="owned")]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
pub struct CreateUpdateTag {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[builder(default)]
|
||||
|
|
@ -189,7 +224,11 @@ pub struct CreateUpdateTag {
|
|||
pub suggestions: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
/// A single tag category. The primary purpose of tag categories is to distinguish certain tag
|
||||
/// types (such as characters, media type etc.), which improves user experience.
|
||||
pub struct TagCategoryResource {
|
||||
|
|
@ -207,8 +246,19 @@ pub struct TagCategoryResource {
|
|||
pub default: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl TagCategoryResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, Builder)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
|
||||
/// Used for creating or updating a Tag Category
|
||||
pub struct CreateUpdateTagCategory {
|
||||
/// Resource version. See [versioning](ResourceVersion)
|
||||
|
|
@ -230,22 +280,29 @@ pub struct CreateUpdateTagCategory {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[builder(setter(into))]
|
||||
/// Removes source tag and merges all of its usages, suggestions and implications to the target tag.
|
||||
/// Other tag properties such as category and aliases do not get transferred and are discarded.
|
||||
pub struct MergeTags {
|
||||
/// Version of the tag to remove
|
||||
pub remove_version: u32,
|
||||
#[serde(rename = "removeVersion")]
|
||||
pub remove_tag_version: u32,
|
||||
/// The name of the tag to remove
|
||||
pub remove: String,
|
||||
#[serde(rename = "remove")]
|
||||
pub remove_tag: String,
|
||||
/// The version of the tag to merge TO
|
||||
pub merge_to_version: u32,
|
||||
/// The name of the tag to merge TO
|
||||
pub merge_to: String,
|
||||
#[serde(rename = "mergeTo")]
|
||||
pub merge_to_tag: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
/// Lists siblings of given tag, e.g. tags that were used in the same posts as the given tag
|
||||
pub struct TagSibling {
|
||||
/// The related tag
|
||||
|
|
@ -254,7 +311,21 @@ pub struct TagSibling {
|
|||
pub occurrences: u32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl TagSibling {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// The type of post
|
||||
pub enum PostType {
|
||||
|
|
@ -277,6 +348,10 @@ pub enum PostType {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// How SFW/NSFW the post is
|
||||
pub enum PostSafety {
|
||||
|
|
@ -284,13 +359,17 @@ pub enum PostSafety {
|
|||
Safe,
|
||||
/// Post is possibly NSFW
|
||||
Sketchy,
|
||||
/// Alias of (Sketchy)[PostSafety::Sketchy]
|
||||
/// Alias of [Sketchy](PostSafety::Sketchy)
|
||||
Questionable,
|
||||
/// Post is NSFW
|
||||
Unsafe,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A post resource stripped down to `id` and `thumbnailUrl` fields.
|
||||
pub struct MicroPostResource {
|
||||
|
|
@ -300,6 +379,16 @@ pub struct MicroPostResource {
|
|||
pub thumbnail_url: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl MicroPostResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for MicroPostResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
if !self.thumbnail_url.contains(url) {
|
||||
|
|
@ -320,6 +409,10 @@ pub(crate) struct PostId {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A post resource
|
||||
pub struct PostResource {
|
||||
|
|
@ -394,6 +487,16 @@ pub struct PostResource {
|
|||
pub pools: Option<Vec<PoolResource>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl PostResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for PostResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
let curl = self.content_url.map(|cu| {
|
||||
|
|
@ -429,7 +532,7 @@ impl WithBaseURL for PostResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A `struct` used to create or update a post. For updating purposes
|
||||
/// the [version](CreateUpdatePost::version) field is required
|
||||
|
|
@ -445,6 +548,7 @@ pub struct CreateUpdatePost {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<String>>,
|
||||
/// Required field, represents the SFW/NSFW state of a post
|
||||
#[builder(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub safety: Option<PostSafety>,
|
||||
/// The origin of the post's content
|
||||
|
|
@ -472,6 +576,10 @@ pub struct CreateUpdatePost {
|
|||
/// [upload_temporary_file](crate::SzurubooruRequest::upload_temporary_file)
|
||||
#[builder(default)]
|
||||
pub content_token: Option<String>,
|
||||
/// Upload the post anonymously
|
||||
#[builder(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub anonymous: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -483,7 +591,7 @@ pub struct TemporaryFileUpload {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
|
||||
#[builder(setter(into))]
|
||||
#[builder(build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Removes source post and merges all of its tags, relations, scores, favorites and comments to
|
||||
/// the target post. If replaceContent is set to true, content of the target post is replaced using
|
||||
|
|
@ -492,15 +600,19 @@ pub struct TemporaryFileUpload {
|
|||
/// and are discarded.
|
||||
pub struct MergePost {
|
||||
/// The version of the post to remove
|
||||
pub remove_version: u32,
|
||||
#[serde(rename = "removeVersion")]
|
||||
pub remove_post_version: u32,
|
||||
/// The ID of the post to remove
|
||||
pub remove: u32,
|
||||
#[serde(rename = "remove")]
|
||||
pub remove_post: u32,
|
||||
/// The version of the post to merge TO
|
||||
pub merge_to_version: u32,
|
||||
/// The post ID of the post to merge TO
|
||||
pub merge_to: u32,
|
||||
#[serde(rename = "mergeTo")]
|
||||
pub merge_to_post: u32,
|
||||
/// Whether to replace the content
|
||||
pub replace_content: bool,
|
||||
#[serde(rename = "replaceContent")]
|
||||
pub replace_post_content: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -510,6 +622,10 @@ pub struct RateResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A text annotation rendered on top of the post
|
||||
pub struct NoteResource {
|
||||
|
|
@ -522,7 +638,21 @@ pub struct NoteResource {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl NoteResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// The Rank of a given User
|
||||
pub enum UserRank {
|
||||
|
|
@ -538,7 +668,11 @@ pub enum UserRank {
|
|||
Administrator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// The kind of User Avatar
|
||||
pub enum UserAvatarStyle {
|
||||
|
|
@ -548,49 +682,175 @@ pub enum UserAvatarStyle {
|
|||
Manual,
|
||||
}
|
||||
|
||||
// Because pyo3 get_all doesn't let you exclude fields we have to define the fields twice
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(all(feature = "python"), pyclass(module = "szurubooru_client.models"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A single user
|
||||
pub struct UserResource {
|
||||
/// Resource version. See [versioning](ResourceVersion)
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub version: Option<u32>,
|
||||
|
||||
/// Resource version. See [versioning](ResourceVersion)
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub version: Option<u32>,
|
||||
|
||||
/// The user's username
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The user's username
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// The user email. It is available only if the request is authenticated by the same user,
|
||||
/// or the authenticated user can change the email. If it's unavailable, the server returns
|
||||
/// `false`. If the user hasn't specified an email, the server returns [None](Option::None)
|
||||
pub email: Option<SzuruEither<String, bool>>,
|
||||
|
||||
/// The user rank, which effectively affects their privileges
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub rank: Option<UserRank>,
|
||||
#[serde(rename = "last-login-time")]
|
||||
|
||||
/// The user rank, which effectively affects their privileges
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub rank: Option<UserRank>,
|
||||
|
||||
/// The last login time
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
#[serde(rename = "last-login-time")]
|
||||
pub last_login_time: Option<DateTime<Utc>>,
|
||||
#[serde(rename = "creation-time")]
|
||||
|
||||
/// The last login time
|
||||
#[cfg(not(feature = "python"))]
|
||||
#[serde(rename = "last-login-time")]
|
||||
pub last_login_time: Option<DateTime<Utc>>,
|
||||
|
||||
/// The user registration time
|
||||
#[serde(rename = "creation-time")]
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub creation_time: Option<DateTime<Utc>>,
|
||||
|
||||
/// The user registration time
|
||||
#[serde(rename = "creation-time")]
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub creation_time: Option<DateTime<Utc>>,
|
||||
|
||||
/// How to render the user avatar
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub avatar_style: Option<UserAvatarStyle>,
|
||||
|
||||
/// How to render the user avatar
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub avatar_style: Option<UserAvatarStyle>,
|
||||
|
||||
/// The URL to the avatar
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
pub avatar_url: Option<String>,
|
||||
|
||||
/// The URL to the avatar
|
||||
#[cfg(not(feature = "python"))]
|
||||
pub avatar_url: Option<String>,
|
||||
|
||||
/// Number of comments
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
#[serde(rename = "comment-count")]
|
||||
pub comment_count: Option<u32>,
|
||||
|
||||
/// Number of comments
|
||||
#[cfg(not(feature = "python"))]
|
||||
#[serde(rename = "comment-count")]
|
||||
pub comment_count: Option<u32>,
|
||||
|
||||
/// Number of uploaded posts
|
||||
#[cfg(feature = "python")]
|
||||
#[pyo3(get)]
|
||||
#[serde(rename = "uploaded-post-count")]
|
||||
pub uploaded_post_count: Option<u32>,
|
||||
|
||||
/// Number of uploaded posts
|
||||
#[cfg(not(feature = "python"))]
|
||||
#[serde(rename = "uploaded-post-count")]
|
||||
pub uploaded_post_count: Option<u32>,
|
||||
|
||||
/// Number of liked posts. It is available only if the request is authenticated by the same
|
||||
/// user. If it's unavailable, the server returns `false`
|
||||
#[serde(rename = "liked-post-count")]
|
||||
pub liked_post_count: Option<SzuruEither<u32, bool>>,
|
||||
|
||||
/// Number of disliked posts. It is available only if the request is authenticated by the same
|
||||
/// user. If it's unavailable, the server returns `false`.
|
||||
#[serde(rename = "disliked-post-count")]
|
||||
pub disliked_post_count: Option<SzuruEither<u32, bool>>,
|
||||
|
||||
/// Number of favorited posts
|
||||
#[serde(rename = "favorite-post-count")]
|
||||
pub favorite_post_count: Option<SzuruEither<u32, bool>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl UserResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
#[pyo3(name = "email")]
|
||||
/// Returns this resource's email field, if the current user has permission to see it
|
||||
pub fn email_py(&self) -> PyResult<Option<String>> {
|
||||
match &self.email {
|
||||
None => Ok(None),
|
||||
Some(SzuruEither::Left(s)) => Ok(Some(s.to_string())),
|
||||
Some(SzuruEither::Right(_)) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
#[pyo3(name = "liked_post_count")]
|
||||
/// Returns this resource's liked_post_count, if the current user has permission to see it
|
||||
pub fn liked_post_count_py(&self) -> PyResult<Option<u32>> {
|
||||
match &self.liked_post_count {
|
||||
None => Ok(None),
|
||||
Some(SzuruEither::Left(s)) => Ok(Some(*s)),
|
||||
Some(SzuruEither::Right(_)) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
#[pyo3(name = "disliked_post_count")]
|
||||
/// Returns this resource's disliked_post_count, if the current user has permission to see it
|
||||
pub fn disliked_post_count_py(&self) -> PyResult<Option<u32>> {
|
||||
match &self.disliked_post_count {
|
||||
None => Ok(None),
|
||||
Some(SzuruEither::Left(s)) => Ok(Some(*s)),
|
||||
Some(SzuruEither::Right(_)) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
#[pyo3(name = "favorite_post_count")]
|
||||
/// Returns this resource's favorite_post_count, if the current user has permission to see it
|
||||
pub fn favorite_post_count_py(&self) -> PyResult<Option<u32>> {
|
||||
match &self.favorite_post_count {
|
||||
None => Ok(None),
|
||||
Some(SzuruEither::Left(s)) => Ok(Some(*s)),
|
||||
Some(SzuruEither::Right(_)) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for UserResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
let av_url = self.avatar_url.map(|au| {
|
||||
|
|
@ -608,7 +868,7 @@ impl WithBaseURL for UserResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, Builder)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// `struct` used to create or update a user resource. The version field is only used when
|
||||
/// updating an existing resource
|
||||
|
|
@ -637,6 +897,10 @@ pub struct CreateUpdateUser {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A user resource stripped down to `name` and `avatarUrl` fields
|
||||
pub struct MicroUserResource {
|
||||
|
|
@ -646,6 +910,16 @@ pub struct MicroUserResource {
|
|||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl MicroUserResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for MicroUserResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
if !self.avatar_url.contains(url) {
|
||||
|
|
@ -660,6 +934,10 @@ impl WithBaseURL for MicroUserResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// A single user token
|
||||
pub struct UserAuthTokenResource {
|
||||
|
|
@ -683,6 +961,16 @@ pub struct UserAuthTokenResource {
|
|||
pub last_usage_time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl UserAuthTokenResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for UserAuthTokenResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
Self {
|
||||
|
|
@ -693,7 +981,7 @@ impl WithBaseURL for UserAuthTokenResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)]
|
||||
#[builder(setter(into, strip_option))]
|
||||
#[builder(setter(into, strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// `struct` to create or update a UserAuthToken. `version` is only required when updating an
|
||||
/// existing resource
|
||||
|
|
@ -733,6 +1021,10 @@ pub struct TemporaryPassword {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Simple server configuration
|
||||
pub struct GlobalInfoConfig {
|
||||
|
|
@ -757,6 +1049,10 @@ pub struct GlobalInfoConfig {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Simple server statistics
|
||||
pub struct GlobalInfo {
|
||||
|
|
@ -776,7 +1072,21 @@ pub struct GlobalInfo {
|
|||
pub config: GlobalInfoConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl GlobalInfo {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A single pool category. The primary purpose of pool categories is to distinguish certain pool
|
||||
/// types (such as series, relations etc.), which improves user experience.
|
||||
|
|
@ -793,8 +1103,18 @@ pub struct PoolCategoryResource {
|
|||
pub default: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl PoolCategoryResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
/// `struct` used for creating or updating a pool category. This type uses a Builder pattern like
|
||||
/// so:
|
||||
///
|
||||
|
|
@ -823,6 +1143,10 @@ pub struct CreateUpdatePoolCategory {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Type that represents a Pool resource
|
||||
pub struct PoolResource {
|
||||
|
|
@ -847,6 +1171,16 @@ pub struct PoolResource {
|
|||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl PoolResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for PoolResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
PoolResource {
|
||||
|
|
@ -857,7 +1191,7 @@ impl WithBaseURL for PoolResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// This type is used when creating or updating a pool object. It uses the builder pattern like so:
|
||||
///
|
||||
|
|
@ -897,6 +1231,7 @@ pub struct CreateUpdatePool {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)]
|
||||
#[builder(build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// This type is used to specify which pools should be merged. Uses the builder pattern like so:
|
||||
///
|
||||
|
|
@ -904,25 +1239,32 @@ pub struct CreateUpdatePool {
|
|||
/// use szurubooru_client::models::MergePoolBuilder;
|
||||
/// // Merge pool ID 1 at version 1 to pool ID 3 at version 5
|
||||
/// let merge_pool = MergePoolBuilder::default()
|
||||
/// .remove_version(1)
|
||||
/// .remove(1)
|
||||
/// .remove_pool_version(1)
|
||||
/// .remove_pool(1)
|
||||
/// .merge_to_version(5)
|
||||
/// .merge_to(3)
|
||||
/// .merge_to_pool(3)
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub struct MergePool {
|
||||
/// Version of the pool to remove. Must match the current Pool version
|
||||
pub remove_version: u32,
|
||||
#[serde(rename = "removeVersion")]
|
||||
pub remove_pool_version: u32,
|
||||
/// Pool ID to remove
|
||||
pub remove: u32,
|
||||
#[serde(rename = "remove")]
|
||||
pub remove_pool: u32,
|
||||
/// Version of the pool to merge TO
|
||||
pub merge_to_version: u32,
|
||||
/// Pool ID of the pool to merge TO
|
||||
pub merge_to: u32,
|
||||
#[serde(rename = "mergeTo")]
|
||||
pub merge_to_pool: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A micro resource representing a Pool. A subset of the fields of a [PoolResource].
|
||||
pub struct MicroPoolResource {
|
||||
|
|
@ -938,7 +1280,21 @@ pub struct MicroPoolResource {
|
|||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl MicroPoolResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A type representing a Comment on a post
|
||||
pub struct CommentResource {
|
||||
|
|
@ -962,8 +1318,18 @@ pub struct CommentResource {
|
|||
pub own_score: Option<i32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl CommentResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)]
|
||||
#[builder(setter(strip_option))]
|
||||
#[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// This type is used when creating or updating a comment. This type uses the builder pattern like
|
||||
/// so:
|
||||
|
|
@ -992,7 +1358,11 @@ pub struct CreateUpdateComment {
|
|||
pub post_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// The kind of snapshot that has been recorded
|
||||
pub enum SnapshotOperationType {
|
||||
|
|
@ -1006,7 +1376,11 @@ pub enum SnapshotOperationType {
|
|||
Merged,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// The kind of resource described by this snapshot
|
||||
pub enum SnapshotResourceType {
|
||||
|
|
@ -1024,7 +1398,11 @@ pub enum SnapshotResourceType {
|
|||
PoolCategory,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
/// Data for a resource that was created
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
|
@ -1041,6 +1419,16 @@ pub enum SnapshotCreationDeletionData {
|
|||
PoolCategory(PoolCategoryResource),
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl SnapshotCreationDeletionData {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for SnapshotCreationDeletionData {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
match self {
|
||||
|
|
@ -1055,13 +1443,25 @@ impl WithBaseURL for SnapshotCreationDeletionData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Data for a modified resource
|
||||
pub struct SnapshotModificationData {
|
||||
/// The type of snapshot
|
||||
#[cfg(feature = "python")]
|
||||
#[serde(rename = "type")]
|
||||
#[pyo3(get)]
|
||||
/// The type of snapshot
|
||||
pub snapshot_type: String,
|
||||
|
||||
#[cfg(not(feature = "python"))]
|
||||
#[serde(rename = "type")]
|
||||
/// The type of snapshot
|
||||
pub snapshot_type: String,
|
||||
|
||||
/// The JSON value for the modified resource. A dictionary diff that depends on the resource
|
||||
/// kind.
|
||||
///
|
||||
|
|
@ -1070,7 +1470,28 @@ pub struct SnapshotModificationData {
|
|||
pub value: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl SnapshotModificationData {
|
||||
#[getter]
|
||||
/// Get the value associated with this snapshot
|
||||
pub fn get_value(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let obj = to_pyobject(py, &self.value).unwrap().unbind();
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(untagged)]
|
||||
/// Type representing the data as part of a snapshot
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
|
@ -1095,6 +1516,10 @@ impl WithBaseURL for SnapshotData {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Overall type representing some sort of change to a resource
|
||||
pub struct SnapshotResource {
|
||||
|
|
@ -1113,6 +1538,16 @@ pub struct SnapshotResource {
|
|||
pub time: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl SnapshotResource {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for SnapshotResource {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
SnapshotResource {
|
||||
|
|
@ -1124,6 +1559,10 @@ impl WithBaseURL for SnapshotResource {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A result when searching for similar posts to a given image
|
||||
pub struct ImageSearchSimilarPost {
|
||||
|
|
@ -1133,6 +1572,16 @@ pub struct ImageSearchSimilarPost {
|
|||
pub post: PostResource,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl ImageSearchSimilarPost {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for ImageSearchSimilarPost {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
Self {
|
||||
|
|
@ -1143,6 +1592,10 @@ impl WithBaseURL for ImageSearchSimilarPost {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A type to represent the result from an Image search request
|
||||
pub struct ImageSearchResult {
|
||||
|
|
@ -1154,6 +1607,16 @@ pub struct ImageSearchResult {
|
|||
pub similar_posts: Vec<ImageSearchSimilarPost>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl ImageSearchResult {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithBaseURL for ImageSearchResult {
|
||||
fn with_base_url(self, url: &str) -> Self {
|
||||
Self {
|
||||
|
|
@ -1164,6 +1627,10 @@ impl WithBaseURL for ImageSearchResult {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(get_all, module = "szurubooru_client.models")
|
||||
)]
|
||||
/// A type that represents posts that are before or after an existing post
|
||||
pub struct AroundPostResult {
|
||||
/// A previous post, if it exists
|
||||
|
|
@ -1172,6 +1639,16 @@ pub struct AroundPostResult {
|
|||
next: Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
#[doc(hidden)]
|
||||
impl AroundPostResult {
|
||||
/// Generates a representative string of this resource
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::{GlobalInfo, GlobalInfoConfig, SnapshotResource, TagCategoryResource};
|
||||
|
|
|
|||
1441
szurubooru-client/src/py/asynchronous.rs
Normal file
54
szurubooru-client/src/py/mod.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use crate::models::PagedSearchResult;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyList;
|
||||
|
||||
// rustfmt likes to break the Python docstrings
|
||||
#[rustfmt::skip]
|
||||
pub mod asynchronous;
|
||||
#[rustfmt::skip]
|
||||
pub mod synchronous;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[pyclass(name = "PagedResult", get_all, module = "szurubooru_client")]
|
||||
/// A paged result generated by most of the ``list`` methods of the Szurubooru clients
|
||||
pub struct PyPagedSearchResult {
|
||||
/// The query string that was used to generate these results
|
||||
pub query: String,
|
||||
/// The offset for the request, how many resource to skip before returning the results
|
||||
pub offset: u32,
|
||||
/// The maximum number of results to return
|
||||
pub limit: u32,
|
||||
/// The total number of results generated by the query
|
||||
pub total: u32,
|
||||
/// The results themselves
|
||||
pub results: Py<PyList>,
|
||||
}
|
||||
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
impl PyPagedSearchResult {
|
||||
fn __repr__(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
/*fn __len__(&self) -> PyResult<usize> {
|
||||
Python::with_gil(|py| {
|
||||
Ok(self.results.bind_borrowed(py).len())
|
||||
})
|
||||
}*/
|
||||
}
|
||||
|
||||
impl<T: IntoPy<PyObject>> From<PagedSearchResult<T>> for PyPagedSearchResult {
|
||||
fn from(value: PagedSearchResult<T>) -> Self {
|
||||
Python::with_gil(|py| {
|
||||
let list =
|
||||
PyList::new_bound(py, value.results.into_iter().map(|v| v.into_py(py))).unbind();
|
||||
PyPagedSearchResult {
|
||||
query: value.query,
|
||||
offset: value.offset,
|
||||
limit: value.limit,
|
||||
total: value.total,
|
||||
results: list,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1565
szurubooru-client/src/py/synchronous.rs
Normal file
|
|
@ -2,6 +2,8 @@
|
|||
//! warned that the types here help with the Type safety for the Tag names only. It does
|
||||
//! not guarantee that a given API endpoint will support the given tag.
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::{exceptions::PyValueError, prelude::*};
|
||||
use std::fmt::Display;
|
||||
use strum_macros::AsRefStr;
|
||||
|
||||
|
|
@ -22,7 +24,8 @@ pub trait ToQueryString {
|
|||
}
|
||||
|
||||
/// A query token using for searching posts, tags and pools
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(all(feature = "python"), pyclass(module = "szurubooru_client.tokens"))]
|
||||
pub struct QueryToken {
|
||||
/// The key for this token. For `foo:bar` this would be `foo`
|
||||
pub key: String,
|
||||
|
|
@ -116,7 +119,7 @@ impl QueryToken {
|
|||
/// let liked_posts = QueryToken::special(PostSpecialToken::Liked);
|
||||
/// client.request().list_posts(Some(&vec![liked_posts]));
|
||||
/// ```
|
||||
pub fn special(key: impl SpecialToken) -> Self {
|
||||
pub fn special(key: impl AsRef<str>) -> Self {
|
||||
QueryToken::anonymous(key)
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +142,210 @@ impl QueryToken {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pyfunction)]
|
||||
/// Generates a named token. Named tokens are used to filter resources returned by the API.
|
||||
/// An example of this would be returning posts with a certain safety value.
|
||||
///
|
||||
/// This function will accept any string, but if you want to be more sure about your code you
|
||||
/// can use any of the :ref:`Named token <named-tokens-enums>` types listed below.
|
||||
/// See the example below.
|
||||
///
|
||||
/// :param key: String or Named field
|
||||
/// :param value: The string or int value to use to filter by
|
||||
/// :returns: The named query token
|
||||
/// :rtype: QueryToken
|
||||
///
|
||||
/// -----
|
||||
/// Usage
|
||||
/// -----
|
||||
/// This lists all posts that are marked as 'safe'.
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_posts(query=[named_token(PostNamedToken.Safety, 'safe')])
|
||||
/// ```
|
||||
///
|
||||
/// Which is equivalent to the possibly more error-prone:
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_posts(query=[named_token("safety", 'safe')])
|
||||
/// ```
|
||||
pub fn named_token(key: &Bound<'_, PyAny>, value: &Bound<'_, PyAny>) -> PyResult<QueryToken> {
|
||||
QueryToken::token_py(key, value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pyfunction)]
|
||||
/// Generates a sorting token. Sorting tokens are used to sort resources returned by the API.
|
||||
/// An example of this would be returning posts by score descending.
|
||||
///
|
||||
/// This function will accept any string, but if you want to be more sure about your code you
|
||||
/// can use any of the :ref:`Sort token <sort-tokens-enums>` types listed below. See the example below.
|
||||
///
|
||||
/// :param key: String or Sort field name
|
||||
/// :returns: The sort query token
|
||||
/// :rtype: QueryToken
|
||||
///
|
||||
/// -----
|
||||
/// Usage
|
||||
/// -----
|
||||
/// This lists posts by score descending:
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_posts(query=[-sort_token(PostSortToken.Score)])
|
||||
/// ```
|
||||
///
|
||||
/// Which is equivalent to the possibly more error-prone:
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_posts(query=[-sort_token("score")])
|
||||
/// ```
|
||||
pub fn sort_token(key: &Bound<'_, PyAny>) -> PyResult<QueryToken> {
|
||||
QueryToken::sort_py(key)
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pyfunction)]
|
||||
/// Generates an anonymous token. Anonymous tokens are tokens that don't require some
|
||||
/// sort of prefix to be used in a search. What the anonymous tag corresponds to depends
|
||||
/// on the type of resource you're listing. For example, when listing posts the anonymous
|
||||
/// tags correspond to post tags
|
||||
///
|
||||
///
|
||||
/// :param str key: The anonymous token to create
|
||||
/// :returns: The anonymous query token
|
||||
/// :rtype: QueryToken
|
||||
///
|
||||
/// -----
|
||||
/// Usage
|
||||
/// -----
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_posts(fields=[anonymous_token("cat")])
|
||||
/// ```
|
||||
pub fn anonymous_token(key: &Bound<'_, PyAny>) -> PyResult<QueryToken> {
|
||||
QueryToken::anonymous_py(key)
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pyfunction)]
|
||||
/// Special tokens are a very limited set of tokens supported by the ``list_posts`` API.
|
||||
/// They include being able to filter by posts that the current user has upvoted, or favorited.
|
||||
/// See :class:`PostSpecialToken` for all the supported token names.
|
||||
///
|
||||
/// :param key: The special token name, string or ``PostSpecialToken``
|
||||
///
|
||||
/// -----
|
||||
/// Usage
|
||||
/// -----
|
||||
///
|
||||
/// Selects posts with score of 0, without comments and without favorites
|
||||
///
|
||||
/// ```python
|
||||
/// client.list_post(fields=[special_token(PostSpecialToken.Tumbleweed)])
|
||||
/// ```
|
||||
pub fn special_token(key: &Bound<'_, PyAny>) -> PyResult<QueryToken> {
|
||||
QueryToken::special_py(key)
|
||||
}
|
||||
|
||||
#[cfg(feature = "python")]
|
||||
#[cfg_attr(all(feature = "python"), pymethods)]
|
||||
impl QueryToken {
|
||||
#[pyo3(name = "__str__")]
|
||||
/// Generates a string representation of this QueryToken
|
||||
pub fn to_python_string(&self) -> PyResult<String> {
|
||||
Ok(format!("QueryToken(\"{}\", \"{}\")", self.key, self.value))
|
||||
}
|
||||
|
||||
#[pyo3(name = "__repr__")]
|
||||
/// Generates a string representation of this QueryToken
|
||||
pub fn to_python_repr(&self) -> PyResult<String> {
|
||||
self.to_python_string()
|
||||
}
|
||||
|
||||
#[pyo3(name = "token")]
|
||||
#[staticmethod]
|
||||
#[doc(hidden)]
|
||||
pub fn token_py(key: &Bound<'_, PyAny>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||
let value = if let Ok(value) = value.extract::<u32>() {
|
||||
value.to_string()
|
||||
} else {
|
||||
value.extract::<String>()?
|
||||
};
|
||||
|
||||
if let Ok(tnt) = key.extract::<TagNamedToken>() {
|
||||
Ok(QueryToken::token(tnt, value))
|
||||
} else if let Ok(pnt) = key.extract::<PostNamedToken>() {
|
||||
Ok(QueryToken::token(pnt, value))
|
||||
} else if let Ok(pnt) = key.extract::<PoolNamedToken>() {
|
||||
Ok(QueryToken::token(pnt, value))
|
||||
} else if let Ok(comment) = key.extract::<CommentNamedToken>() {
|
||||
Ok(QueryToken::token(comment, value))
|
||||
} else if let Ok(user) = key.extract::<UserNamedToken>() {
|
||||
Ok(QueryToken::token(user, value))
|
||||
} else if let Ok(x) = key.extract::<SnapshotNamedToken>() {
|
||||
Ok(QueryToken::token(x, value))
|
||||
} else if let Ok(strvalue) = key.extract::<String>() {
|
||||
Ok(QueryToken::token(strvalue, value))
|
||||
} else {
|
||||
Err(PyErr::new::<PyValueError, _>("Invalid value type for key"))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyo3(name = "sort")]
|
||||
#[staticmethod]
|
||||
#[doc(hidden)]
|
||||
pub fn sort_py(key: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||
if let Ok(tnt) = key.extract::<TagSortToken>() {
|
||||
Ok(QueryToken::sort(tnt))
|
||||
} else if let Ok(pnt) = key.extract::<PostSortToken>() {
|
||||
Ok(QueryToken::sort(pnt))
|
||||
} else if let Ok(pnt) = key.extract::<PoolSortToken>() {
|
||||
Ok(QueryToken::sort(pnt))
|
||||
} else if let Ok(comment) = key.extract::<CommentSortToken>() {
|
||||
Ok(QueryToken::sort(comment))
|
||||
} else if let Ok(user) = key.extract::<UserSortToken>() {
|
||||
Ok(QueryToken::sort(user))
|
||||
} else if let Ok(strvalue) = key.extract::<String>() {
|
||||
Ok(QueryToken::sort(strvalue))
|
||||
} else {
|
||||
Err(PyErr::new::<PyValueError, _>("Invalid value type for key"))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyo3(name = "anonymous")]
|
||||
#[staticmethod]
|
||||
#[doc(hidden)]
|
||||
pub fn anonymous_py(key: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||
let key = key.extract::<String>()?;
|
||||
Ok(QueryToken::anonymous(key))
|
||||
}
|
||||
|
||||
#[pyo3(name = "special")]
|
||||
#[staticmethod]
|
||||
#[doc(hidden)]
|
||||
pub fn special_py(key: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||
if let Ok(special) = key.extract::<PostSpecialToken>() {
|
||||
Ok(QueryToken::special(special))
|
||||
} else if let Ok(strvalue) = key.extract::<String>() {
|
||||
Ok(QueryToken::special(strvalue))
|
||||
} else {
|
||||
Err(PyErr::new::<PyValueError, _>("Invalid value type for key"))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyo3(name = "negate")]
|
||||
#[doc(hidden)]
|
||||
pub fn negate_py(&self) -> PyResult<Self> {
|
||||
Ok(self.negate())
|
||||
}
|
||||
|
||||
/// Negates the query token. Would turn ``konosuba`` into ``-konosuba``
|
||||
pub fn __neg__(&self) -> PyResult<Self> {
|
||||
Ok(self.negate())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QueryToken {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let suffix = if !self.value.is_empty() {
|
||||
|
|
@ -157,8 +364,12 @@ impl ToQueryString for Vec<QueryToken> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with [list_tags](crate::SzurubooruRequest::list_tags)
|
||||
pub enum TagNamedToken {
|
||||
/// having given name (accepts wildcards)
|
||||
|
|
@ -188,8 +399,28 @@ pub enum TagNamedToken {
|
|||
}
|
||||
impl NamedToken for TagNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
/*#[cfg(feature="python")]
|
||||
impl<'py> FromPyObject<'py> for TagNamedToken {
|
||||
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
|
||||
/*use pyo3::exceptions::PyTypeError;
|
||||
if ob.is_instance_of::<TagNamedToken>() {
|
||||
Ok()
|
||||
}
|
||||
let strvalue = ob.extract::<String>()?;
|
||||
match TagNamedToken::from_str(&strvalue) {
|
||||
Ok(tnt) => Ok(tnt),
|
||||
Err(_) => Err(PyTypeError::new_err("Invalid variant"))
|
||||
}*/
|
||||
Ok(ob.downcast_into_exact::<Self>()?.)
|
||||
}
|
||||
}*/
|
||||
|
||||
#[derive(Debug, AsRefStr, Eq, PartialEq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe sort query tokens for use with [list_tags](crate::SzurubooruRequest::list_tags)
|
||||
pub enum TagSortToken {
|
||||
/// as random as it can get
|
||||
|
|
@ -223,8 +454,12 @@ pub enum TagSortToken {
|
|||
}
|
||||
impl SortableToken for TagSortToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with [list_posts](crate::SzurubooruRequest::list_posts)
|
||||
pub enum PostNamedToken {
|
||||
/// having given post number
|
||||
|
|
@ -259,8 +494,9 @@ pub enum PostNamedToken {
|
|||
RelationCount,
|
||||
/// having been featured given number of times
|
||||
FeatureCount,
|
||||
/// given type of posts. `value` can be either `image`, `animation` (or `animated` or `anim`),
|
||||
/// `flash` (or `swf`) or `video` (or `webm`). Use [models::PostType] for type-safe values
|
||||
/// given type of posts. The value can be either `image`, `animation` (or `animated` or `anim`),
|
||||
/// `flash` (or `swf`) or `video` (or `webm`). Use [PostType](crate::models::PostType)
|
||||
/// for type-safe values
|
||||
Type,
|
||||
/// having given SHA1 checksum
|
||||
ContentChecksum,
|
||||
|
|
@ -312,16 +548,20 @@ pub enum PostNamedToken {
|
|||
FeatureDate,
|
||||
/// alias of [PostNamedToken::FeatureDate]
|
||||
FeatureTime,
|
||||
/// having given safety. <value> can be either `safe`, `sketchy` (or `questionable`) or `unsafe`
|
||||
/// Use [models::PostSafety] for the type-safe version
|
||||
/// Post safety. Can be either `safe`, `sketchy` (or `questionable`) or `unsafe`
|
||||
/// Use [PostSafety](crate::models::PostSafety) for the type-safe version
|
||||
Safety,
|
||||
/// alias of [PostNamedToken::Safety]
|
||||
Rating,
|
||||
}
|
||||
impl NamedToken for PostNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe sort query tokens for use with [list_posts](crate::SzurubooruRequest::list_posts)
|
||||
pub enum PostSortToken {
|
||||
/// as random as it can get
|
||||
|
|
@ -364,7 +604,7 @@ pub enum PostSortToken {
|
|||
Date,
|
||||
/// alias of [PostSortToken::CreationDate]
|
||||
Time,
|
||||
/// like [PostSortToken::CreationDate], only looks at last edit time
|
||||
/// like [PostSortToken::CreationDate], only looks at last edit time instead
|
||||
LastEditDate,
|
||||
/// alias of [PostSortToken::LastEditDate]
|
||||
LastEditTime,
|
||||
|
|
@ -387,8 +627,12 @@ pub enum PostSortToken {
|
|||
}
|
||||
impl SortableToken for PostSortToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe special query tokens for use with [list_posts](crate::SzurubooruRequest::list_posts)
|
||||
pub enum PostSpecialToken {
|
||||
/// posts liked by currently logged-in user
|
||||
|
|
@ -402,8 +646,12 @@ pub enum PostSpecialToken {
|
|||
}
|
||||
impl SpecialToken for PostSpecialToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with [list_pools](crate::SzurubooruRequest::list_pools)
|
||||
pub enum PoolNamedToken {
|
||||
/// having given name (accepts wildcards)
|
||||
|
|
@ -427,8 +675,12 @@ pub enum PoolNamedToken {
|
|||
}
|
||||
impl NamedToken for PoolNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe sort query tokens for use with [list_pools](crate::SzurubooruRequest::list_pools)
|
||||
pub enum PoolSortToken {
|
||||
/// as random as it can get
|
||||
|
|
@ -454,8 +706,12 @@ pub enum PoolSortToken {
|
|||
}
|
||||
impl SortableToken for PoolSortToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with
|
||||
/// [list_comments](crate::SzurubooruRequest::list_comments)
|
||||
pub enum CommentNamedToken {
|
||||
|
|
@ -484,8 +740,12 @@ pub enum CommentNamedToken {
|
|||
}
|
||||
impl NamedToken for CommentNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe sort query tokens for use with
|
||||
/// [list_comments](crate::SzurubooruRequest::list_comments)
|
||||
pub enum CommentSortToken {
|
||||
|
|
@ -512,8 +772,12 @@ pub enum CommentSortToken {
|
|||
}
|
||||
impl SortableToken for CommentSortToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with [list_users](crate::SzurubooruRequest::list_users)
|
||||
pub enum UserNamedToken {
|
||||
/// having given name (accepts wildcards)
|
||||
|
|
@ -533,8 +797,12 @@ pub enum UserNamedToken {
|
|||
}
|
||||
impl NamedToken for UserNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe sort query tokens for use with [list_users](crate::SzurubooruRequest::list_users)
|
||||
pub enum UserSortToken {
|
||||
/// as random as it can get
|
||||
|
|
@ -556,8 +824,12 @@ pub enum UserSortToken {
|
|||
}
|
||||
impl SortableToken for UserNamedToken {}
|
||||
|
||||
#[derive(Debug, AsRefStr)]
|
||||
#[derive(Debug, AsRefStr, PartialEq, Eq, Clone)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[cfg_attr(
|
||||
all(feature = "python"),
|
||||
pyclass(eq, eq_int, module = "szurubooru_client.tokens")
|
||||
)]
|
||||
/// Type-safe named query tokens for use with
|
||||
/// [list_snapshots](crate::SzurubooruRequest::list_snapshots)
|
||||
pub enum SnapshotNamedToken {
|
||||
|
|
|
|||
4
szurubooru-client/szurubooru_client/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .szurubooru_client import *
|
||||
|
||||
__doc__ = szurubooru_client.__doc__
|
||||
__all__ = ["SzurubooruSyncClient", "SzurubooruAsyncClient", "SzuruClientError", "PagedResult"]
|
||||
34
szurubooru-client/szurubooru_client/models.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from .szurubooru_client import _models
|
||||
|
||||
AroundPostResult = _models.AroundPostResult
|
||||
CommentResource = _models.CommentResource
|
||||
GlobalInfo = _models.GlobalInfo
|
||||
ImageSearchResult = _models.ImageSearchResult
|
||||
ImageSearchSimilarPost = _models.ImageSearchSimilarPost
|
||||
MicroPoolResource = _models.MicroPoolResource
|
||||
MicroPostResource = _models.MicroPostResource
|
||||
MicroTagResource = _models.MicroTagResource
|
||||
MicroUserResource = _models.MicroUserResource
|
||||
NoteResource = _models.NoteResource
|
||||
PoolCategoryResource = _models.PoolCategoryResource
|
||||
PoolResource = _models.PoolResource
|
||||
PostResource = _models.PostResource
|
||||
PostSafety = _models.PostSafety
|
||||
PostType = _models.PostType
|
||||
SnapshotCreationDeletionData = _models.SnapshotCreationDeletionData
|
||||
SnapshotData = _models.SnapshotData
|
||||
SnapshotModificationData = _models.SnapshotModificationData
|
||||
SnapshotOperationType = _models.SnapshotOperationType
|
||||
SnapshotResource = _models.SnapshotResource
|
||||
SnapshotResourceType = _models.SnapshotResourceType
|
||||
TagCategoryResource = _models.TagCategoryResource
|
||||
TagResource = _models.TagResource
|
||||
TagSibling = _models.TagSibling
|
||||
UserAuthTokenResource = _models.UserAuthTokenResource
|
||||
UserAvatarStyle = _models.UserAvatarStyle
|
||||
UserRank = _models.UserRank
|
||||
UserResource = _models.UserResource
|
||||
|
||||
__doc__ = _models.__doc__
|
||||
#if hasattr(_models, "__all__"):
|
||||
# __all__ = getattr(_models, "__all__")
|
||||
23
szurubooru-client/szurubooru_client/tokens.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from .szurubooru_client import _tokens
|
||||
|
||||
anonymous_token = _tokens.anonymous_token
|
||||
named_token = _tokens.named_token
|
||||
sort_token = _tokens.sort_token
|
||||
special_token = _tokens.special_token
|
||||
CommentNamedToken = _tokens.CommentNamedToken
|
||||
CommentSortToken = _tokens.CommentSortToken
|
||||
PoolNamedToken = _tokens.PoolNamedToken
|
||||
PoolSortToken = _tokens.PoolSortToken
|
||||
PostNamedToken = _tokens.PostNamedToken
|
||||
PostSortToken = _tokens.PostSortToken
|
||||
PostSpecialToken = _tokens.PostSpecialToken
|
||||
QueryToken = _tokens.QueryToken
|
||||
SnapshotNamedToken = _tokens.SnapshotNamedToken
|
||||
TagNamedToken = _tokens.TagNamedToken
|
||||
TagSortToken = _tokens.TagSortToken
|
||||
UserNamedToken = _tokens.UserNamedToken
|
||||
UserSortToken = _tokens.UserSortToken
|
||||
|
||||
__doc__ = _tokens.__doc__
|
||||
#if hasattr(_tokens, "__all__"):
|
||||
# __all__ = getattr(_tokens, "__all__")
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
#export MOUNT_DATA=szurubooru/data
|
||||
export SERVER_MOUNT=./szurubooru/server
|
||||
#export MOUNT_SQL=szurubooru/pgsql
|
||||
export BASE_URL=http://localhost:9801
|
||||
export PORT=9801
|
||||
|
||||
docker compose down || true
|
||||
|
||||
docker compose up -d
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
docker compose down
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 310 KiB |
179
tests/python-sync/config.yaml
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# rather than editing this file, it is strongly suggested to create config.yaml
|
||||
# and override only what you need.
|
||||
|
||||
# shown in the website title and on the front page
|
||||
name: integrationland
|
||||
# full url to the homepage of this szurubooru site, with no trailing slash
|
||||
domain: https://localhost:9802
|
||||
# used to salt the users' password hashes and generate filenames for static content
|
||||
secret: awdopk1mdqsapdoawd21!!!#asw
|
||||
|
||||
# Delete thumbnails and source files on post delete
|
||||
# Original functionality is no, to mitigate the impacts of admins going
|
||||
# on unchecked post purges.
|
||||
delete_source_files: no
|
||||
|
||||
thumbnails:
|
||||
avatar_width: 300
|
||||
avatar_height: 300
|
||||
post_width: 300
|
||||
post_height: 300
|
||||
|
||||
# settings used to download files from the web on behalf of the api users
|
||||
user_agent:
|
||||
max_dl_filesize: 25.0E+6 # maximum filesize limit in bytes
|
||||
|
||||
# automatically convert animated GIF uploads to video formats
|
||||
convert:
|
||||
gif:
|
||||
to_webm: false
|
||||
to_mp4: false
|
||||
|
||||
# allow posts to be uploaded even if some image processing errors occur
|
||||
allow_broken_uploads: false
|
||||
|
||||
# used to send password reset e-mails
|
||||
smtp:
|
||||
host: # example: localhost
|
||||
port: # example: 25
|
||||
user: # example: bot
|
||||
pass: # example: groovy123
|
||||
from: # example: noreply@example.com
|
||||
# if host is left empty the password reset feature will be disabled,
|
||||
# in which case it is recommended to fill contactEmail so that users
|
||||
# know who to contact when they want to reset their password
|
||||
|
||||
contact_email: # example: bob@example.com. Meant for manual password reset procedures
|
||||
|
||||
enable_safety: yes
|
||||
|
||||
tag_name_regex: ^\S+$
|
||||
tag_category_name_regex: ^[^\s%+#/]+$
|
||||
|
||||
pool_name_regex: ^\S+$
|
||||
pool_category_name_regex: ^[^\s%+#/]+$
|
||||
|
||||
# don't make these more restrictive unless you want to annoy people; if you do
|
||||
# customize them, make sure to update the instructions in the registration form
|
||||
# template as well.
|
||||
password_regex: '^.{5,}$'
|
||||
user_name_regex: '^[a-zA-Z0-9_-]{1,32}$'
|
||||
|
||||
# webhooks to call when events occur (such as post/tag/user/etc. changes)
|
||||
# the listed urls will be called with a HTTP POST request with a payload
|
||||
# containing a snapshot resource as JSON. See doc/API.md for details
|
||||
webhooks:
|
||||
# - https://api.example.com/webhooks/
|
||||
|
||||
default_rank: regular
|
||||
|
||||
privileges:
|
||||
'users:create:self': anonymous # Registration permission
|
||||
'users:create:any': administrator
|
||||
'users:list': regular
|
||||
'users:view': regular
|
||||
'users:edit:any:name': moderator
|
||||
'users:edit:any:pass': moderator
|
||||
'users:edit:any:email': moderator
|
||||
'users:edit:any:avatar': moderator
|
||||
'users:edit:any:rank': moderator
|
||||
'users:edit:self:name': regular
|
||||
'users:edit:self:pass': regular
|
||||
'users:edit:self:email': regular
|
||||
'users:edit:self:avatar': regular
|
||||
'users:edit:self:rank': moderator # one can't promote themselves or anyone to upper rank than their own.
|
||||
'users:delete:any': administrator
|
||||
'users:delete:self': regular
|
||||
|
||||
'user_tokens:list:any': administrator
|
||||
'user_tokens:list:self': regular
|
||||
'user_tokens:create:any': administrator
|
||||
'user_tokens:create:self': regular
|
||||
'user_tokens:edit:any': administrator
|
||||
'user_tokens:edit:self': regular
|
||||
'user_tokens:delete:any': administrator
|
||||
'user_tokens:delete:self': regular
|
||||
|
||||
'posts:create:anonymous': regular
|
||||
'posts:create:identified': regular
|
||||
'posts:list': anonymous
|
||||
'posts:reverse_search': regular
|
||||
'posts:view': anonymous
|
||||
'posts:view:featured': anonymous
|
||||
'posts:edit:content': power
|
||||
'posts:edit:flags': regular
|
||||
'posts:edit:notes': regular
|
||||
'posts:edit:relations': regular
|
||||
'posts:edit:safety': power
|
||||
'posts:edit:source': regular
|
||||
'posts:edit:tags': regular
|
||||
'posts:edit:thumbnail': power
|
||||
'posts:feature': moderator
|
||||
'posts:delete': moderator
|
||||
'posts:score': regular
|
||||
'posts:merge': moderator
|
||||
'posts:favorite': regular
|
||||
'posts:bulk-edit:tags': power
|
||||
'posts:bulk-edit:safety': power
|
||||
'posts:bulk-edit:delete': power
|
||||
|
||||
'tags:create': regular
|
||||
'tags:edit:names': power
|
||||
'tags:edit:category': power
|
||||
'tags:edit:description': power
|
||||
'tags:edit:implications': power
|
||||
'tags:edit:suggestions': power
|
||||
'tags:list': regular
|
||||
'tags:view': anonymous
|
||||
'tags:merge': moderator
|
||||
'tags:delete': moderator
|
||||
|
||||
'tag_categories:create': moderator
|
||||
'tag_categories:edit:name': moderator
|
||||
'tag_categories:edit:color': moderator
|
||||
'tag_categories:edit:order': moderator
|
||||
'tag_categories:list': anonymous
|
||||
'tag_categories:view': anonymous
|
||||
'tag_categories:delete': moderator
|
||||
'tag_categories:set_default': moderator
|
||||
|
||||
'pools:create': regular
|
||||
'pools:edit:names': power
|
||||
'pools:edit:category': power
|
||||
'pools:edit:description': power
|
||||
'pools:edit:posts': power
|
||||
'pools:list': regular
|
||||
'pools:view': anonymous
|
||||
'pools:merge': moderator
|
||||
'pools:delete': moderator
|
||||
|
||||
'pool_categories:create': moderator
|
||||
'pool_categories:edit:name': moderator
|
||||
'pool_categories:edit:color': moderator
|
||||
'pool_categories:list': anonymous
|
||||
'pool_categories:view': anonymous
|
||||
'pool_categories:delete': moderator
|
||||
'pool_categories:set_default': moderator
|
||||
|
||||
'comments:create': regular
|
||||
'comments:delete:any': moderator
|
||||
'comments:delete:own': regular
|
||||
'comments:edit:any': moderator
|
||||
'comments:edit:own': regular
|
||||
'comments:list': regular
|
||||
'comments:view': regular
|
||||
'comments:score': regular
|
||||
|
||||
'snapshots:list': power
|
||||
|
||||
'uploads:create': regular
|
||||
'uploads:use_downloader': power
|
||||
|
||||
## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER
|
||||
#debug: 0 # generate server logs?
|
||||
#show_sql: 0 # show sql in server logs?
|
||||
#data_url: /data/
|
||||
#data_dir: /var/www/data
|
||||
## usage: schema://user:password@host:port/database_name
|
||||
## example: postgres://szuru:dog@localhost:5432/szuru_test
|
||||
#database:
|
||||
|
|
@ -24,7 +24,7 @@ services:
|
|||
THREADS:
|
||||
volumes:
|
||||
- "sz-data:/data"
|
||||
- "${SERVER_MOUNT}/config.yaml:/opt/app/config.yaml"
|
||||
- "./config.yaml:/opt/app/config.yaml"
|
||||
|
||||
client:
|
||||
image: szurubooru/client:latest
|
||||
|
|
@ -32,11 +32,11 @@ services:
|
|||
- server
|
||||
environment:
|
||||
BACKEND_HOST: server
|
||||
BASE_URL: ${BASE_URL}
|
||||
BASE_URL: http://localhost:9802
|
||||
volumes:
|
||||
- "sz-data:/data:ro"
|
||||
ports:
|
||||
- "${PORT}:80"
|
||||
- "9802:80"
|
||||
|
||||
sql:
|
||||
image: postgres:11-alpine
|
||||
6
tests/python-sync/requirements.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Jinja2==3.1.4
|
||||
MarkupSafe==2.1.5
|
||||
maturin==1.7.0
|
||||
pdoc==14.6.0
|
||||
Pygments==2.18.0
|
||||
loguru==0.7.2
|
||||
366
tests/python-sync/test.py
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
from szurubooru_client import *
|
||||
from szurubooru_client.tokens import *
|
||||
from szurubooru_client.models import *
|
||||
import time, sys
|
||||
from loguru import logger
|
||||
import hashlib
|
||||
import tempfile, pathlib
|
||||
|
||||
def connect():
|
||||
logger.info("Connecting to Szurubooru instance")
|
||||
anon_client = SzurubooruSyncClient("http://localhost:9802")
|
||||
error = None
|
||||
for i in range(5):
|
||||
try:
|
||||
anon_client.global_info()
|
||||
except Exception as e:
|
||||
error=e
|
||||
time.sleep(5)
|
||||
else:
|
||||
logger.info("Connection successful!")
|
||||
break
|
||||
else:
|
||||
logger.error("Could not connect to Szurubooru instance, error is {}", error)
|
||||
sys.exit(1)
|
||||
return anon_client
|
||||
|
||||
def create_auth_client(client):
|
||||
client.create_user("integration_user", "integration_password", rank=UserRank.Administrator)
|
||||
auth_client = SzurubooruSyncClient("http://localhost:9802", username="integration_user",
|
||||
password="integration_password", allow_insecure=True)
|
||||
return auth_client
|
||||
|
||||
def test_tag_categories(client):
|
||||
logger.info("Testing tag categories")
|
||||
logger.info("Listing tag categories")
|
||||
tag_cats = client.list_tag_categories()
|
||||
assert len(tag_cats) == 1
|
||||
|
||||
logger.info("Creating tag category")
|
||||
result_tag_cat = client.create_tag_category("my_tag_cat", color="purple", order=1)
|
||||
assert result_tag_cat.name == "my_tag_cat"
|
||||
|
||||
tag_cats = client.list_tag_categories()
|
||||
assert len(tag_cats) != 1
|
||||
assert tag_cats[1].name == "my_tag_cat"
|
||||
|
||||
logger.info("Getting tag category")
|
||||
get_tag_cat = client.get_tag_category("my_tag_cat")
|
||||
assert get_tag_cat.color == result_tag_cat.color
|
||||
|
||||
logger.info("Updating tag category")
|
||||
update_tag_cat = client.update_tag_category("my_tag_cat", get_tag_cat.version, color="red")
|
||||
assert update_tag_cat.color != get_tag_cat.color
|
||||
|
||||
logger.info("Deleting tag category")
|
||||
client.delete_tag_category("my_tag_cat", update_tag_cat.version)
|
||||
tag_cats = client.list_tag_categories()
|
||||
assert len(tag_cats) == 1
|
||||
|
||||
def test_tag(client):
|
||||
logger.info("Testing tags")
|
||||
logger.info("Listing tags")
|
||||
|
||||
tags = client.list_tags()
|
||||
assert len(tags.results) == 0
|
||||
|
||||
logger.info("Creating tag")
|
||||
foo_tag = client.create_tag("foo", category="default", description="The foo tag")
|
||||
assert foo_tag.names == ["foo"]
|
||||
tags = client.list_tags()
|
||||
assert len(tags.results) == 1
|
||||
|
||||
logger.info("Testing field selection")
|
||||
tags = client.list_tags(fields=["version", "names", "category"])
|
||||
assert len(tags.results) == 1
|
||||
assert tags.results[0].description is None
|
||||
|
||||
logger.info("Updating tag")
|
||||
foo_tag = client.update_tag(foo_tag.names[0], version=foo_tag.version, description="The foo2 tag")
|
||||
assert foo_tag.description == "The foo2 tag"
|
||||
|
||||
logger.info("Getting tag")
|
||||
foo_tag2 = client.get_tag("foo")
|
||||
assert foo_tag.description == foo_tag2.description
|
||||
|
||||
logger.info("Creating a second tag")
|
||||
bar_tag = client.create_tag("bar", category="default", description="The bar tag")
|
||||
assert bar_tag.names == ["bar"]
|
||||
|
||||
logger.info("Merging tags")
|
||||
foo_tag2 = client.merge_tags(bar_tag.names[0], bar_tag.version,
|
||||
foo_tag2.names[0], foo_tag2.version)
|
||||
tags = client.list_tags()
|
||||
assert len(tags.results) == 1
|
||||
|
||||
logger.info("Deleting tag")
|
||||
client.delete_tag(foo_tag2.names[0], foo_tag2.version)
|
||||
|
||||
def test_creating_posts(client):
|
||||
logger.info("Testing posts")
|
||||
|
||||
logger.info("Listing posts")
|
||||
posts = client.list_posts()
|
||||
assert len(posts.results) == 0
|
||||
|
||||
logger.info("Creating post from URL")
|
||||
wiki_post = client.create_post(url="https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Maine_Coon_cat_by_Tomitheos.JPG/225px-Maine_Coon_cat_by_Tomitheos.JPG",
|
||||
safety=PostSafety.Safe)
|
||||
posts = client.list_posts()
|
||||
assert len(posts.results) == 1
|
||||
|
||||
logger.info("Updating post")
|
||||
wiki_post = client.update_post(wiki_post.id, wiki_post.version, source="Wikipedia")
|
||||
assert wiki_post.source == "Wikipedia"
|
||||
|
||||
logger.info("Deleting post")
|
||||
client.delete_post(wiki_post.id, wiki_post.version)
|
||||
posts = client.list_posts()
|
||||
assert len(posts.results) == 0
|
||||
|
||||
logger.info("Testing upload by file path")
|
||||
folly1 = client.create_post(file_path="../folly1.jpg",
|
||||
tags=["maine_coon", "cat", "folly1"],
|
||||
safety=PostSafety.Safe)
|
||||
posts = client.list_posts()
|
||||
assert len(posts.results) == 1
|
||||
|
||||
folly2 = client.create_post(file_path="../folly2.jpg",
|
||||
tags=["maine_coon", "cat", "folly2"],
|
||||
safety=PostSafety.Safe)
|
||||
|
||||
logger.info("Testing upload with thumbnail")
|
||||
folly3 = client.create_post(file_path="../folly3.jpg",
|
||||
thumbnail_path="../folly3_thumb.jpg",
|
||||
tags=["maine_coon", "cat", "folly3"],
|
||||
safety=PostSafety.Safe)
|
||||
|
||||
logger.info("Searching for a post with image")
|
||||
folly3_search = client.post_for_image("../folly3.jpg")
|
||||
assert folly3_search is not None
|
||||
assert folly3_search.id == folly3.id
|
||||
|
||||
logger.info("Reverse image searching")
|
||||
reverse_search = client.reverse_image_search("../folly3.jpg")
|
||||
assert reverse_search.exact_post.id == folly3.id
|
||||
|
||||
logger.info("Testing temporary upload")
|
||||
token = client.upload_temporary_file("../folly4.jpg")
|
||||
folly4 = client.create_post(upload_token=token, tags=["maine_coon", "cat", "folly4"],
|
||||
safety=PostSafety.Safe)
|
||||
|
||||
logger.info("Querying by anonymous tag")
|
||||
cat_posts = client.list_posts(query=[anonymous_token("cat")])
|
||||
assert len(cat_posts.results) == 4
|
||||
|
||||
logger.info("Querying by named tag")
|
||||
cat_posts = client.list_posts(query=[named_token(PostNamedToken.Tag, "maine_coon")])
|
||||
assert len(cat_posts.results) == 4
|
||||
|
||||
logger.info("Testing pagination")
|
||||
cat_posts = client.list_posts(limit=1)
|
||||
assert cat_posts.total == 4
|
||||
assert len(cat_posts.results) == 1
|
||||
|
||||
cat_posts2 = client.list_posts(limit=1, offset=1)
|
||||
assert cat_posts.results != cat_posts2.results
|
||||
|
||||
logger.info("Testing tag siblings")
|
||||
tag_occurrences = client.get_tag_siblings("maine_coon")
|
||||
cat_occurrences = list(filter(lambda x: "cat" in x.tag.names, tag_occurrences))
|
||||
assert len(cat_occurrences) == 1
|
||||
|
||||
logger.info("Rating post")
|
||||
client.rate_post(folly3.id, 1)
|
||||
|
||||
logger.info("Testing rating error validation")
|
||||
try:
|
||||
client.rate_post(folly3.id, -2)
|
||||
except ValueError:
|
||||
assert True
|
||||
else:
|
||||
assert False
|
||||
|
||||
logger.info("Favoriting post")
|
||||
client.favorite_post(folly3.id)
|
||||
|
||||
logger.info("Unfavoriting post")
|
||||
client.unfavorite_post(folly3.id)
|
||||
|
||||
logger.info("Featuring post")
|
||||
client.set_featured_post(folly3.id)
|
||||
|
||||
logger.info("Getting featured post")
|
||||
featured_post = client.get_featured_post()
|
||||
assert folly3.id == featured_post.id
|
||||
|
||||
logger.info("Merging posts")
|
||||
merged_post = client.merge_post(folly4.id, folly4.version, folly3.id, folly3.version)
|
||||
assert merged_post.id == folly3.id
|
||||
|
||||
def test_pool_categories(client):
|
||||
logger.info("Testing pool categories")
|
||||
|
||||
logger.info("Listing pool categories")
|
||||
pool_cats = client.list_pool_categories()
|
||||
assert len(pool_cats) != 0
|
||||
|
||||
logger.info("Creating pool category")
|
||||
pool_cat = client.create_pool_category("cat_pool_category", color="purple")
|
||||
assert pool_cat.color == "purple"
|
||||
pool_dog = client.create_pool_category("dog_category", color="orange")
|
||||
|
||||
logger.info("Updating pool category")
|
||||
pool_cat = client.update_pool_category(pool_cat.name, pool_cat.version, color="white")
|
||||
assert pool_cat.color == "white"
|
||||
|
||||
logger.info("Getting pool category")
|
||||
pool_dog = client.get_pool_category(pool_dog.name)
|
||||
assert pool_dog.color == "orange"
|
||||
|
||||
logger.info("Deleting pool category")
|
||||
pool_dog = client.delete_pool_category(pool_dog.name, pool_dog.version)
|
||||
|
||||
logger.info("Setting default pool category")
|
||||
client.set_default_pool_category(pool_cat.name)
|
||||
|
||||
def test_pools(client):
|
||||
logger.info("Testing post pools")
|
||||
logger.info("Listing post pools")
|
||||
pools = client.list_pools()
|
||||
assert len(pools.results) == 0
|
||||
|
||||
logger.info("Creating pools")
|
||||
cat_pool = client.create_pool("cats_pool", category="cat_pool_category")
|
||||
catz_pool = client.create_pool("catz_pool", category="cat_pool_category")
|
||||
dogs_pool = client.create_pool("dogs_pool", category="cat_pool_category")
|
||||
|
||||
logger.info("Getting pool")
|
||||
cat_pool = client.get_pool(cat_pool.id)
|
||||
|
||||
logger.info("Deleting pool")
|
||||
client.delete_pool(dogs_pool.id, dogs_pool.version)
|
||||
|
||||
logger.info("Updating pool")
|
||||
f4_results = client.list_posts([anonymous_token("cat")])
|
||||
post_ids= list(map(lambda p: p.id, f4_results.results))
|
||||
cat_pool = client.update_pool(cat_pool.id, cat_pool.version,
|
||||
posts=post_ids, description="All cat pictures")
|
||||
assert len(cat_pool.posts) != 0
|
||||
|
||||
logger.info("Merging pools")
|
||||
merged_pool = client.merge_pools(catz_pool.id, catz_pool.version, cat_pool.id, cat_pool.version)
|
||||
assert merged_pool.id == cat_pool.id
|
||||
|
||||
def test_comments(client):
|
||||
logger.info("Testing post comments")
|
||||
|
||||
logger.info("Listing post comments")
|
||||
comment_list = client.list_comments()
|
||||
assert len(comment_list.results) == 0
|
||||
|
||||
cat_results = client.list_posts([anonymous_token("cat")])
|
||||
post_id = cat_results.results[0].id
|
||||
|
||||
logger.info("Creating comment")
|
||||
comment = client.create_comment("Excellent cat!", post_id)
|
||||
|
||||
logger.info("Updating comment")
|
||||
comment = client.update_comment(comment.id, comment.version, text="Beautiful cat!")
|
||||
assert comment.text == "Beautiful cat!"
|
||||
|
||||
logger.info("Getting comment")
|
||||
comment = client.get_comment(comment.id)
|
||||
assert comment.text == "Beautiful cat!"
|
||||
|
||||
logger.info("Getting all comments for post")
|
||||
comment_list = client.list_comments([named_token(CommentNamedToken.Post, post_id)])
|
||||
assert len(comment_list.results) != 0
|
||||
|
||||
logger.info("Testing rating comments")
|
||||
comment = client.rate_comment(comment.id, -1)
|
||||
assert comment.own_score == -1
|
||||
|
||||
try:
|
||||
client.rate_comment(comment.id, -2)
|
||||
except SzuruClientError:
|
||||
assert True
|
||||
else:
|
||||
assert False
|
||||
|
||||
logger.info("Deleting comment")
|
||||
client.delete_comment(comment.id, comment.version)
|
||||
|
||||
def test_users(client):
|
||||
logger.info("Testing users")
|
||||
|
||||
logger.info("Listing users")
|
||||
user_list = client.list_users()
|
||||
|
||||
logger.info("Creating user with avatar")
|
||||
user = client.create_user("iu2", "ipass2", rank=UserRank.Regular, avatar_path="../avatar.jpg")
|
||||
assert user.avatar_style == UserAvatarStyle.Manual
|
||||
|
||||
logger.info("Updating user")
|
||||
user = client.update_user(user.name, user.version, rank=UserRank.Restricted)
|
||||
|
||||
logger.info("Getting user")
|
||||
user = client.get_user(user.name)
|
||||
|
||||
logger.info("Deleting user")
|
||||
client.delete_user(user.name, user.version)
|
||||
|
||||
logger.info("Listing user tokens")
|
||||
tokens = client.list_user_tokens("integration_user")
|
||||
assert len(tokens) == 0
|
||||
|
||||
logger.info("Creating user token")
|
||||
token = client.create_user_token("integration_user", "My token")
|
||||
|
||||
logger.info("Updating user token")
|
||||
token2 = client.update_user_token("integration_user", token.token, token.version, enabled=False)
|
||||
assert token2.enabled == False
|
||||
|
||||
logger.info("Deleting user token")
|
||||
client.delete_user_token("integration_user", token2.token, token2.version)
|
||||
|
||||
def test_snapshots(client):
|
||||
logger.info("Testing snapshots")
|
||||
|
||||
logger.info("Listing snapshots")
|
||||
snapshot_list = client.list_snapshots()
|
||||
assert len(snapshot_list.results) != 0
|
||||
|
||||
def test_downloads(client):
|
||||
logger.info("Testing downloads")
|
||||
f3_hasher = hashlib.new("sha1")
|
||||
|
||||
with open("../folly3.jpg", "rb") as f:
|
||||
while (byte := f.read(1)):
|
||||
f3_hasher.update(byte)
|
||||
|
||||
f3_post = client.list_posts([anonymous_token("folly3")]).results[0]
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
tmpdir = pathlib.Path(tmpdirname)
|
||||
fname = tmpdir / "folly3_dl.jpg"
|
||||
#fname = "../folly4_dl.jpg"
|
||||
client.download_image_to_path(f3_post.id, fname)
|
||||
dl_hasher = hashlib.new("sha1")
|
||||
with open(fname, "rb") as f:
|
||||
while (byte := f.read(1)):
|
||||
dl_hasher.update(byte)
|
||||
|
||||
assert f3_hasher.hexdigest() == dl_hasher.hexdigest()
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = connect()
|
||||
client = create_auth_client(client)
|
||||
test_tag_categories(client)
|
||||
test_tag(client)
|
||||
test_creating_posts(client)
|
||||
test_pool_categories(client)
|
||||
test_pools(client)
|
||||
test_comments(client)
|
||||
test_users(client)
|
||||
test_snapshots(client)
|
||||
test_downloads(client)
|
||||
9
tests/python-sync/test.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
pip uninstall -y szurubooru_client || true
|
||||
pip install -r requirements.txt
|
||||
maturin develop -F python -m ../../szurubooru-client/Cargo.toml
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
python test.py && docker compose down
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "szurubooru-integration-test"
|
||||
name = "rust-test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
sha1 = "0.10.6"
|
||||
szurubooru-client = { path = "../szurubooru-client" }
|
||||
szurubooru-client = { path = "../../szurubooru-client" }
|
||||
tempfile = "3.12.0"
|
||||
tokio = { version = "1.39.2", features = ["full", "test-util", "tracing"] }
|
||||
tracing = "0.1.40"
|
||||
51
tests/rust-test/docker-compose.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
## Example Docker Compose configuration
|
||||
##
|
||||
## Use this as a template to set up docker-compose, or as guide to set up other
|
||||
## orchestration services
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
|
||||
server:
|
||||
image: szurubooru/server:latest
|
||||
depends_on:
|
||||
- sql
|
||||
environment:
|
||||
## These should be the names of the dependent containers listed below,
|
||||
## or FQDNs/IP addresses if these services are running outside of Docker
|
||||
POSTGRES_HOST: sql
|
||||
## Credentials for database:
|
||||
POSTGRES_USER: pguser
|
||||
POSTGRES_PASSWORD: pgpassword
|
||||
## Commented Values are Default:
|
||||
#POSTGRES_DB: defaults to same as POSTGRES_USER
|
||||
#POSTGRES_PORT: 5432
|
||||
#LOG_SQL: 0 (1 for verbose SQL logs)
|
||||
THREADS:
|
||||
volumes:
|
||||
- "sz-data:/data"
|
||||
- "./config.yaml:/opt/app/config.yaml"
|
||||
|
||||
client:
|
||||
image: szurubooru/client:latest
|
||||
depends_on:
|
||||
- server
|
||||
environment:
|
||||
BACKEND_HOST: server
|
||||
BASE_URL: http://localhost:9801
|
||||
volumes:
|
||||
- "sz-data:/data:ro"
|
||||
ports:
|
||||
- "9801:80"
|
||||
|
||||
sql:
|
||||
image: postgres:11-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: pguser
|
||||
POSTGRES_PASSWORD: pgpassword
|
||||
#volumes:
|
||||
# - "${MOUNT_SQL}/sqldata:/var/lib/postgresql/data"
|
||||
|
||||
volumes:
|
||||
sz-data:
|
||||
|
|
@ -77,21 +77,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
#[tracing::instrument]
|
||||
async fn start_instance() -> SzurubooruClient {
|
||||
info!("Starting Szurubooru instance...");
|
||||
info!("Connecting to Szurubooru instance...");
|
||||
let anon_client = SzurubooruClient::new_anonymous("http://localhost:9801", true)
|
||||
.expect("Can't create anonymous client");
|
||||
|
||||
Command::new("sh")
|
||||
.current_dir(env!("CARGO_MANIFEST_DIR"))
|
||||
.arg("-c")
|
||||
.arg("./start_szurubooru.sh")
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stdin(Stdio::null())
|
||||
.status()
|
||||
.await
|
||||
.expect("Failed to start szurubooru");
|
||||
|
||||
let mut connected = false;
|
||||
let mut error = None;
|
||||
|
||||
|
|
@ -220,7 +209,11 @@ async fn test_tags(client: &SzurubooruClient) {
|
|||
|
||||
info!("Testing field selection");
|
||||
let tag_list = client
|
||||
.with_fields(vec!["version", "names", "category"])
|
||||
.with_fields(vec![
|
||||
"version".to_string(),
|
||||
"names".to_string(),
|
||||
"category".to_string(),
|
||||
])
|
||||
.list_tags(None)
|
||||
.await
|
||||
.expect("Could not list tags");
|
||||
|
|
@ -269,15 +262,15 @@ async fn test_tags(client: &SzurubooruClient) {
|
|||
|
||||
info!("Merging tags");
|
||||
let merge_tag = MergeTagsBuilder::default()
|
||||
.remove_version(bar_tag.version)
|
||||
.remove(bar_tag.names.as_ref().unwrap().first().unwrap())
|
||||
.remove_tag_version(bar_tag.version)
|
||||
.remove_tag(bar_tag.names.as_ref().unwrap().first().unwrap().clone())
|
||||
.merge_to_version(tag_res3.version)
|
||||
.merge_to(tag_res3.names.as_ref().unwrap().first().unwrap())
|
||||
.merge_to_tag(tag_res3.names.as_ref().unwrap().first().unwrap().clone())
|
||||
.build()
|
||||
.expect("Could not create merge tags object");
|
||||
let merged_tag = client
|
||||
.request()
|
||||
.merge_tag(&merge_tag)
|
||||
.merge_tags(&merge_tag)
|
||||
.await
|
||||
.expect("Could not merge tags");
|
||||
assert_eq!(tag_res3.names, merged_tag.names);
|
||||
|
|
@ -357,7 +350,7 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
.safety(PostSafety::Safe)
|
||||
.build()
|
||||
.expect("Could not build first upload object");
|
||||
let folly1_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly1.jpg");
|
||||
let folly1_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly1.jpg");
|
||||
let mut folly1_file =
|
||||
File::open(&folly1_path).expect(&format!("Could not open file {folly1_path:?}"));
|
||||
let _folly1_post = client
|
||||
|
|
@ -376,7 +369,7 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
.safety(PostSafety::Safe)
|
||||
.build()
|
||||
.expect("Could not build second upload object");
|
||||
let folly2_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly2.jpg");
|
||||
let folly2_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly2.jpg");
|
||||
let _folly2_post = client
|
||||
.request()
|
||||
.create_post_from_file_path(folly2_path, None::<String>, &folly2_obj)
|
||||
|
|
@ -393,8 +386,8 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
.safety(PostSafety::Safe)
|
||||
.build()
|
||||
.expect("Could not build third upload object");
|
||||
let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly3.jpg");
|
||||
let folly3_thumbnail = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly3_thumb.jpg");
|
||||
let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly3.jpg");
|
||||
let folly3_thumbnail = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly3_thumb.jpg");
|
||||
let folly3_post = client
|
||||
.request()
|
||||
.create_post_from_file_path(&folly3_path, Some(folly3_thumbnail), &folly3_obj)
|
||||
|
|
@ -404,10 +397,10 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
info!("Searching for post by image");
|
||||
let matching_posts = client
|
||||
.request()
|
||||
.posts_for_file_path(&folly3_path)
|
||||
.post_for_file_path(&folly3_path)
|
||||
.await
|
||||
.expect("Could not search for post by file path");
|
||||
assert_eq!(matching_posts.results.first().unwrap(), &folly3_post);
|
||||
assert_eq!(&matching_posts.unwrap(), &folly3_post);
|
||||
|
||||
info!("Reverse searching");
|
||||
let matching_posts = client
|
||||
|
|
@ -418,7 +411,7 @@ async fn test_creating_posts(client: &SzurubooruClient) {
|
|||
assert!(matching_posts.exact_post.is_some());
|
||||
|
||||
info!("Testing temporary upload");
|
||||
let folly4_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly4.jpg");
|
||||
let folly4_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly4.jpg");
|
||||
let folly4_temp_upload = client
|
||||
.request()
|
||||
.upload_temporary_file_from_path(folly4_path)
|
||||
|
|
@ -681,10 +674,10 @@ async fn test_pools(client: &SzurubooruClient) {
|
|||
|
||||
info!("Merging pools");
|
||||
let merge_pool_obj = MergePoolBuilder::default()
|
||||
.remove_version(catz_pool.version.unwrap())
|
||||
.remove(catz_pool.id.unwrap())
|
||||
.remove_pool_version(catz_pool.version.unwrap())
|
||||
.remove_pool(catz_pool.id.unwrap())
|
||||
.merge_to_version(cat_pool.version.unwrap())
|
||||
.merge_to(cat_pool.id.unwrap())
|
||||
.merge_to_pool(cat_pool.id.unwrap())
|
||||
.build()
|
||||
.expect("Unable to build merge object");
|
||||
let _cat_pool = client
|
||||
|
|
@ -790,7 +783,7 @@ async fn test_users(client: &SzurubooruClient) {
|
|||
|
||||
// Create user is already tested above
|
||||
info!("Creating user with avatar");
|
||||
let avatar_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("avatar.jpg");
|
||||
let avatar_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../avatar.jpg");
|
||||
let create_user = CreateUpdateUserBuilder::default()
|
||||
.name("iu2".to_string())
|
||||
.password("ipass2".to_string())
|
||||
|
|
@ -892,7 +885,7 @@ async fn test_snapshots(client: &SzurubooruClient) {
|
|||
async fn test_downloads(client: &SzurubooruClient) {
|
||||
info!("Testing image download");
|
||||
|
||||
let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly3.jpg");
|
||||
let folly3_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../folly3.jpg");
|
||||
|
||||
let mut f3_hasher = Sha1::new();
|
||||
|
||||
6
tests/rust-test/test.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
cargo check
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
cargo run && docker compose down
|
||||