Merge pull request #328 from arcbtc/FastAPI

latest fusion44 commits
This commit is contained in:
Arc 2021-09-02 10:47:57 +01:00 committed by GitHub
commit 9e68a242e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1461 additions and 580 deletions

16
Pipfile
View File

@ -14,28 +14,22 @@ environs = "*"
lnurl = "==0.3.6"
pyscss = "*"
shortuuid = "*"
quart = "*"
quart-cors = "*"
quart-compress = "*"
typing-extensions = "*"
httpx = "*"
quart-trio = "*"
trio = "==0.16.0"
sqlalchemy-aio = "*"
embit = "*"
pyqrcode = "*"
pypng = "*"
sqlalchemy = "==1.3.23"
psycopg2-binary = "*"
aiofiles = "*"
asyncio = "*"
fastapi = "*"
uvicorn = {extras = ["standard"], version = "*"}
sse-starlette = "*"
[dev-packages]
black = "==20.8b1"
pytest = "*"
pytest-cov = "*"
mypy = "latest"
pytest-trio = "*"
trio-typing = "*"
[packages.hypercorn]
extras = [ "trio",]
version = "*"

856
Pipfile.lock generated Normal file
View File

@ -0,0 +1,856 @@
{
"_meta": {
"hash": {
"sha256": "e26f678c4b89a86400e0a62396d06e360bfdf1e0f922d474ded200ee1ffde5c4"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4",
"sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"
],
"index": "pypi",
"version": "==0.7.0"
},
"anyio": {
"hashes": [
"sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0",
"sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.3.0"
},
"asgiref": {
"hashes": [
"sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
"sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
],
"markers": "python_version >= '3.6'",
"version": "==3.4.1"
},
"asyncio": {
"hashes": [
"sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41",
"sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de",
"sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c",
"sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"
],
"index": "pypi",
"version": "==3.4.3"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"bech32": {
"hashes": [
"sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899",
"sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
},
"bitstring": {
"hashes": [
"sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578",
"sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7",
"sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f"
],
"index": "pypi",
"version": "==3.1.9"
},
"cerberus": {
"hashes": [
"sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"
],
"index": "pypi",
"version": "==1.3.4"
},
"certifi": {
"hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
],
"version": "==2021.5.30"
},
"charset-normalizer": {
"hashes": [
"sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
"sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
],
"markers": "python_version >= '3.5'",
"version": "==2.0.4"
},
"click": {
"hashes": [
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.1"
},
"ecdsa": {
"hashes": [
"sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676",
"sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"
],
"index": "pypi",
"version": "==0.17.0"
},
"embit": {
"hashes": [
"sha256:19f69929caf0d2fcfd4b708dd873384dfc36267944d02d5e6dfebc835f294e1b"
],
"index": "pypi",
"version": "==0.4.6"
},
"environs": {
"hashes": [
"sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c",
"sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"
],
"index": "pypi",
"version": "==9.3.3"
},
"fastapi": {
"hashes": [
"sha256:644bb815bae326575c4b2842469fb83053a4b974b82fa792ff9283d17fbbd99d",
"sha256:94d2820906c36b9b8303796fb7271337ec89c74223229e3cfcf056b5a7d59e23"
],
"index": "pypi",
"version": "==0.68.1"
},
"h11": {
"hashes": [
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
],
"markers": "python_version >= '3.6'",
"version": "==0.12.0"
},
"httpcore": {
"hashes": [
"sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e",
"sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"
],
"markers": "python_version >= '3.6'",
"version": "==0.13.6"
},
"httptools": {
"hashes": [
"sha256:01b392a166adcc8bc2f526a939a8aabf89fe079243e1543fd0e7dc1b58d737cb",
"sha256:200fc1cdf733a9ff554c0bb97a4047785cfaad9875307d6087001db3eb2b417f",
"sha256:3ab1f390d8867f74b3b5ee2a7ecc9b8d7f53750bd45714bf1cb72a953d7dfa77",
"sha256:78d03dd39b09c99ec917d50189e6743adbfd18c15d5944392d2eabda688bf149",
"sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5",
"sha256:80ffa04fe8c8dfacf6e4cef8277347d35b0442c581f5814f3b0cf41b65c43c6e",
"sha256:813871f961edea6cb2fe312f2d9b27d12a51ba92545380126f80d0de1917ea15",
"sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0",
"sha256:a23166e5ae2775709cf4f7ad4c2048755ebfb272767d244e1a96d55ac775cca7",
"sha256:a289c27ccae399a70eacf32df9a44059ca2ba4ac444604b00a19a6c1f0809943",
"sha256:a7594f9a010cdf1e16a58b3bf26c9da39bbf663e3b8d46d39176999d71816658",
"sha256:b08d00d889a118f68f37f3c43e359aab24ee29eb2e3fe96d64c6a2ba8b9d6557",
"sha256:cc9be041e428c10f8b6ab358c6b393648f9457094e1dcc11b4906026d43cd380",
"sha256:d5682eeb10cca0606c4a8286a3391d4c3c5a36f0c448e71b8bd05be4e1694bfb",
"sha256:fd3b8905e21431ad306eeaf56644a68fdd621bf8f3097eff54d0f6bdf7262065"
],
"version": "==0.2.0"
},
"httpx": {
"hashes": [
"sha256:92ecd2c00c688b529eda11cedb15161eaf02dee9116712f621c70d9a40b2cdd0",
"sha256:9bd728a6c5ec0a9e243932a9983d57d3cc4a87bb4f554e1360fce407f78f9435"
],
"index": "pypi",
"version": "==0.19.0"
},
"idna": {
"hashes": [
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"version": "==3.2"
},
"importlib-metadata": {
"hashes": [
"sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15",
"sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"
],
"markers": "python_version < '3.8'",
"version": "==4.8.1"
},
"lnurl": {
"hashes": [
"sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92",
"sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"
],
"index": "pypi",
"version": "==0.3.6"
},
"marshmallow": {
"hashes": [
"sha256:c67929438fd73a2be92128caa0325b1b5ed8b626d91a094d2f7f2771bf1f1c0e",
"sha256:dd4724335d3c2b870b641ffe4a2f8728a1380cd2e7e2312756715ffeaa82b842"
],
"markers": "python_version >= '3.5'",
"version": "==3.13.0"
},
"outcome": {
"hashes": [
"sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958",
"sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"
],
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
"psycopg2-binary": {
"hashes": [
"sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975",
"sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd",
"sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616",
"sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2",
"sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90",
"sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a",
"sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e",
"sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d",
"sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed",
"sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a",
"sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140",
"sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32",
"sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31",
"sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a",
"sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917",
"sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf",
"sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7",
"sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0",
"sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72",
"sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698",
"sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773",
"sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68",
"sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76",
"sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4",
"sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f",
"sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34",
"sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce",
"sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a",
"sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"
],
"index": "pypi",
"version": "==2.9.1"
},
"pydantic": {
"hashes": [
"sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd",
"sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739",
"sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f",
"sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840",
"sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23",
"sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287",
"sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62",
"sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b",
"sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb",
"sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820",
"sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3",
"sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b",
"sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e",
"sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3",
"sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316",
"sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b",
"sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4",
"sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20",
"sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e",
"sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505",
"sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1",
"sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==1.8.2"
},
"pypng": {
"hashes": [
"sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"
],
"index": "pypi",
"version": "==0.0.21"
},
"pyqrcode": {
"hashes": [
"sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6",
"sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"
],
"index": "pypi",
"version": "==1.2.1"
},
"pyscss": {
"hashes": [
"sha256:f1df571569021a23941a538eb154405dde80bed35dc1ea7c5f3e18e0144746bf"
],
"index": "pypi",
"version": "==1.3.7"
},
"python-dotenv": {
"hashes": [
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
],
"markers": "python_version >= '3.5'",
"version": "==0.19.0"
},
"pyyaml": {
"hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"version": "==5.4.1"
},
"represent": {
"hashes": [
"sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0",
"sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.6.0.post0"
},
"rfc3986": {
"extras": [
"idna2008"
],
"hashes": [
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
],
"version": "==1.5.0"
},
"shortuuid": {
"hashes": [
"sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f",
"sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"
],
"index": "pypi",
"version": "==1.0.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
},
"sqlalchemy": {
"hashes": [
"sha256:040bdfc1d76a9074717a3f43455685f781c581f94472b010cd6c4754754e1862",
"sha256:1fe5d8d39118c2b018c215c37b73fd6893c3e1d4895be745ca8ff6eb83333ed3",
"sha256:23927c3981d1ec6b4ea71eb99d28424b874d9c696a21e5fbd9fa322718be3708",
"sha256:24f9569e82a009a09ce2d263559acb3466eba2617203170e4a0af91e75b4f075",
"sha256:2578dbdbe4dbb0e5126fb37ffcd9793a25dcad769a95f171a2161030bea850ff",
"sha256:269990b3ab53cb035d662dcde51df0943c1417bdab707dc4a7e4114a710504b4",
"sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1",
"sha256:37b83bf81b4b85dda273aaaed5f35ea20ad80606f672d94d2218afc565fb0173",
"sha256:63677d0c08524af4c5893c18dbe42141de7178001360b3de0b86217502ed3601",
"sha256:639940bbe1108ac667dcffc79925db2966826c270112e9159439ab6bb14f8d80",
"sha256:6a939a868fdaa4b504e8b9d4a61f21aac11e3fecc8a8214455e144939e3d2aea",
"sha256:6b8b8c80c7f384f06825612dd078e4a31f0185e8f1f6b8c19e188ff246334205",
"sha256:6c9e6cc9237de5660bcddea63f332428bb83c8e2015c26777281f7ffbd2efb84",
"sha256:6ec1044908414013ebfe363450c22f14698803ce97fbb47e53284d55c5165848",
"sha256:6fca33672578666f657c131552c4ef8979c1606e494f78cd5199742dfb26918b",
"sha256:751934967f5336a3e26fc5993ccad1e4fee982029f9317eb6153bc0bc3d2d2da",
"sha256:8be835aac18ec85351385e17b8665bd4d63083a7160a017bef3d640e8e65cadb",
"sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5",
"sha256:94208867f34e60f54a33a37f1c117251be91a47e3bfdb9ab8a7847f20886ad06",
"sha256:94f667d86be82dd4cb17d08de0c3622e77ca865320e0b95eae6153faa7b4ecaf",
"sha256:9e9c25522933e569e8b53ccc644dc993cab87e922fb7e142894653880fdd419d",
"sha256:a0e306e9bb76fd93b29ae3a5155298e4c1b504c7cbc620c09c20858d32d16234",
"sha256:a8bfc1e1afe523e94974132d7230b82ca7fa2511aedde1f537ec54db0399541a",
"sha256:ac2244e64485c3778f012951fdc869969a736cd61375fde6096d08850d8be729",
"sha256:b4b0e44d586cd64b65b507fa116a3814a1a53d55dce4836d7c1a6eb2823ff8d1",
"sha256:baeb451ee23e264de3f577fee5283c73d9bbaa8cb921d0305c0bbf700094b65b",
"sha256:c7dc052432cd5d060d7437e217dd33c97025287f99a69a50e2dc1478dd610d64",
"sha256:d1a85dfc5dee741bf49cb9b6b6b8d2725a268e4992507cf151cba26b17d97c37",
"sha256:d90010304abb4102123d10cbad2cdf2c25a9f2e66a50974199b24b468509bad5",
"sha256:ddfb511e76d016c3a160910642d57f4587dc542ce5ee823b0d415134790eeeb9",
"sha256:e273367f4076bd7b9a8dc2e771978ef2bfd6b82526e80775a7db52bff8ca01dd",
"sha256:e5bb3463df697279e5459a7316ad5a60b04b0107f9392e88674d0ece70e9cf70",
"sha256:e8a1750b44ad6422ace82bf3466638f1aa0862dbb9689690d5f2f48cce3476c8",
"sha256:eab063a70cca4a587c28824e18be41d8ecc4457f8f15b2933584c6c6cccd30f0",
"sha256:ecce8c021894a77d89808222b1ff9687ad84db54d18e4bd0500ca766737faaf6",
"sha256:f4d972139d5000105fcda9539a76452039434013570d6059993120dc2a65e447",
"sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec",
"sha256:fdd2ed7395df8ac2dbb10cefc44737b66c6a5cd7755c92524733d7a443e5b7e2"
],
"index": "pypi",
"version": "==1.3.23"
},
"sqlalchemy-aio": {
"hashes": [
"sha256:7f77366f55d34891c87386dd0962a28b948b684e8ea5edb7daae4187c0b291bf",
"sha256:f767320427c22c66fa5840a1f17f3261110a8ddc8560558f4fbf12d31a66b17b"
],
"index": "pypi",
"version": "==0.16.0"
},
"sse-starlette": {
"hashes": [
"sha256:1c0cc62cc7d021a386dc06a16a9ddc3e2861d19da6bc2e654e65cc111e820456"
],
"index": "pypi",
"version": "==0.6.2"
},
"starlette": {
"hashes": [
"sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed",
"sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"
],
"markers": "python_version >= '3.6'",
"version": "==0.14.2"
},
"typing-extensions": {
"hashes": [
"sha256:045dd532231acfa03628df5e0c66dba64e2cc8fc8b844538d4ad6d5dd6cb82dc",
"sha256:83af6730a045fda60f46510f7f1f094776d90321caa4d97d20ef38871bef4bd3",
"sha256:8bbffbd37fbeb9747a0241fdfde5ae99d4531ad1d1a41ccaea62100e15a5814c"
],
"index": "pypi",
"version": "==3.10.0.1"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
"sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1",
"sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"
],
"index": "pypi",
"version": "==0.15.0"
},
"uvloop": {
"hashes": [
"sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450",
"sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897",
"sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861",
"sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c",
"sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805",
"sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d",
"sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464",
"sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f",
"sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9",
"sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab",
"sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f",
"sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638",
"sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64",
"sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee",
"sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382",
"sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"
],
"version": "==0.16.0"
},
"watchgod": {
"hashes": [
"sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29",
"sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"
],
"version": "==0.7"
},
"websockets": {
"hashes": [
"sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc",
"sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e",
"sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135",
"sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02",
"sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3",
"sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf",
"sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b",
"sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2",
"sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af",
"sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d",
"sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880",
"sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077",
"sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f",
"sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec",
"sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25",
"sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0",
"sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe",
"sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a",
"sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb",
"sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d",
"sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857",
"sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c",
"sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0",
"sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40",
"sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4",
"sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20",
"sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314",
"sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da",
"sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58",
"sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2",
"sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd",
"sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a",
"sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"
],
"version": "==9.1"
},
"zipp": {
"hashes": [
"sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
"sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
],
"markers": "python_version >= '3.6'",
"version": "==3.5.0"
}
},
"develop": {
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
"version": "==1.4.4"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"black": {
"hashes": [
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
],
"index": "pypi",
"version": "==20.8b1"
},
"click": {
"hashes": [
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.1"
},
"coverage": {
"hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==5.5"
},
"importlib-metadata": {
"hashes": [
"sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15",
"sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"
],
"markers": "python_version < '3.8'",
"version": "==4.8.1"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"mypy": {
"hashes": [
"sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9",
"sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a",
"sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9",
"sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e",
"sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2",
"sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212",
"sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b",
"sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885",
"sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150",
"sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703",
"sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072",
"sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457",
"sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e",
"sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0",
"sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb",
"sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97",
"sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8",
"sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811",
"sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6",
"sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de",
"sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504",
"sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921",
"sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"
],
"index": "pypi",
"version": "==0.910"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"markers": "python_version >= '3.6'",
"version": "==21.0"
},
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"py": {
"hashes": [
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
],
"index": "pypi",
"version": "==6.2.4"
},
"pytest-cov": {
"hashes": [
"sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a",
"sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"
],
"index": "pypi",
"version": "==2.12.1"
},
"regex": {
"hashes": [
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
],
"version": "==2021.8.28"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"typed-ast": {
"hashes": [
"sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
"sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff",
"sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266",
"sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528",
"sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6",
"sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808",
"sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4",
"sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363",
"sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341",
"sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04",
"sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41",
"sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e",
"sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3",
"sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899",
"sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805",
"sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c",
"sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c",
"sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39",
"sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a",
"sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3",
"sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7",
"sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f",
"sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075",
"sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0",
"sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40",
"sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428",
"sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927",
"sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3",
"sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f",
"sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"
],
"markers": "python_version < '3.8'",
"version": "==1.4.3"
},
"typing-extensions": {
"hashes": [
"sha256:045dd532231acfa03628df5e0c66dba64e2cc8fc8b844538d4ad6d5dd6cb82dc",
"sha256:83af6730a045fda60f46510f7f1f094776d90321caa4d97d20ef38871bef4bd3",
"sha256:8bbffbd37fbeb9747a0241fdfde5ae99d4531ad1d1a41ccaea62100e15a5814c"
],
"index": "pypi",
"version": "==3.10.0.1"
},
"zipp": {
"hashes": [
"sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
"sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
],
"markers": "python_version >= '3.6'",
"version": "==3.5.0"
}
}
}

View File

@ -1,28 +1,21 @@
from hypercorn.trio import serve
import trio
import trio_asyncio
from hypercorn.config import Config
import asyncio
from .commands import migrate_databases, transpile_scss, bundle_vendored
import uvloop
from starlette.requests import Request
trio.run(migrate_databases)
from .commands import bundle_vendored, migrate_databases, transpile_scss
from .settings import (DEBUG, LNBITS_COMMIT, LNBITS_DATA_FOLDER,
LNBITS_SITE_TITLE, PORT, SERVICE_FEE, WALLET)
uvloop.install()
asyncio.create_task(migrate_databases())
transpile_scss()
bundle_vendored()
from .app import create_app
app = trio.run(create_app)
from .settings import (
LNBITS_SITE_TITLE,
SERVICE_FEE,
DEBUG,
LNBITS_DATA_FOLDER,
WALLET,
LNBITS_COMMIT,
HOST,
PORT
)
app = create_app()
print(
f"""Starting LNbits with
@ -35,6 +28,3 @@ print(
"""
)
config = Config()
config.bind = [f"{HOST}:{PORT}"]
trio_asyncio.run(serve, app, config)

View File

@ -1,38 +1,30 @@
import jinja2
from lnbits.jinja2_templating import Jinja2Templates
import sys
import warnings
import asyncio
import importlib
from lnbits.core.tasks import register_task_listeners
import sys
import traceback
import trio
import warnings
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from .commands import db_migrate, handle_assets
from .core import core_app
from .helpers import (
get_valid_extensions,
get_js_vendored,
get_css_vendored,
url_for_vendored,
)
from .proxy_fix import ASGIProxyFix
from .tasks import (
webhook_handler,
invoice_listener,
run_deferred_async,
check_pending_payments,
internal_invoice_listener,
catch_everything_and_restart,
)
from .settings import WALLET
from .requestvars import g, request_global
import lnbits.settings
async def create_app(config_object="lnbits.settings") -> FastAPI:
from .commands import db_migrate, handle_assets
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (get_css_vendored, get_js_vendored, get_valid_extensions,
template_renderer, url_for_vendored)
from .requestvars import g
from .settings import WALLET
from .tasks import (catch_everything_and_restart, check_pending_payments, internal_invoice_listener,
invoice_listener, run_deferred_async, webhook_handler)
def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
@ -54,7 +46,16 @@ async def create_app(config_object="lnbits.settings") -> FastAPI:
)
g().config = lnbits.settings
g().templates = build_standard_jinja_templates()
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return template_renderer().TemplateResponse("error.html", {"request": request, "err": f"`{exc.errors()}` is not a valid UUID."})
# return HTMLResponse(
# status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
# content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
# )
app.add_middleware(GZipMiddleware, minimum_size=1000)
# app.add_middleware(ASGIProxyFix)
@ -68,26 +69,6 @@ async def create_app(config_object="lnbits.settings") -> FastAPI:
return app
def build_standard_jinja_templates():
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(["lnbits/templates", "lnbits/core/templates"]),
)
t.env.globals["SITE_TITLE"] = lnbits.settings.LNBITS_SITE_TITLE
t.env.globals["SITE_TAGLINE"] = lnbits.settings.LNBITS_SITE_TAGLINE
t.env.globals["SITE_DESCRIPTION"] = lnbits.settings.LNBITS_SITE_DESCRIPTION
t.env.globals["LNBITS_THEME_OPTIONS"] = lnbits.settings.LNBITS_THEME_OPTIONS
t.env.globals["LNBITS_VERSION"] = lnbits.settings.LNBITS_COMMIT
t.env.globals["EXTENSIONS"] = get_valid_extensions()
if g().config.DEBUG:
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())
else:
t.env.globals["VENDORED_JS"] = ["/static/bundle.js"]
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
return t
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
async def check_wallet_status():
@ -106,8 +87,9 @@ def check_funding_source(app: FastAPI) -> None:
def register_routes(app: FastAPI) -> None:
"""Register Flask blueprints / LNbits extensions."""
"""Register FastAPI routes / LNbits extensions."""
app.include_router(core_app)
app.include_router(core_html_routes)
for ext in get_valid_extensions():
try:
@ -147,36 +129,23 @@ def register_async_tasks(app):
@app.on_event("startup")
async def listeners():
run_deferred_async()
trio.open_process(check_pending_payments)
trio.open_process(invoice_listener)
trio.open_process(internal_invoice_listener)
async with trio.open_nursery() as n:
pass
# n.start_soon(catch_everything_and_restart, check_pending_payments)
# n.start_soon(catch_everything_and_restart, invoice_listener)
# n.start_soon(catch_everything_and_restart, internal_invoice_listener)
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(check_pending_payments))
loop.create_task(catch_everything_and_restart(invoice_listener))
loop.create_task(catch_everything_and_restart(internal_invoice_listener))
await register_task_listeners()
await run_deferred_async()
@app.on_event("shutdown")
async def stop_listeners():
pass
def register_exception_handlers(app):
@app.errorhandler(Exception)
async def basic_error(err):
async def basic_error(request: Request, err):
print("handled error", traceback.format_exc())
etype, value, tb = sys.exc_info()
traceback.print_exception(etype, err, tb)
exc = traceback.format_exc()
return (
"\n\n".join(
[
"LNbits internal error!",
exc,
"If you believe this shouldn't be an error please bring it up on https://t.me/lnbits",
]
),
500,
)
return template_renderer().TemplateResponse("error.html", {"request": request, "err": err})

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import warnings
import click
import importlib
@ -18,7 +18,7 @@ from .settings import LNBITS_PATH
@click.command("migrate")
def db_migrate():
trio.run(migrate_databases)
asyncio.create_task(migrate_databases())
@click.command("assets")

View File

@ -6,14 +6,8 @@ db = Database("database")
core_app: APIRouter = APIRouter()
from lnbits.tasks import record_async
from .tasks import register_listeners
from .views.api import * # noqa
from .views.generic import * # noqa
from .views.public_api import * # noqa
@core_app.on_event("startup")
def do_startup():
record_async(register_listeners)

View File

@ -54,20 +54,14 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
""",
(user_id,),
)
else:
return None
return (
User(
**{
**user,
**{
"extensions": [e[0] for e in extensions],
"wallets": [Wallet(**w) for w in wallets],
},
}
)
if user
else None
)
return User(
id = user['id'],
email = user['email'],
extensions = [e[0] for e in extensions],
wallets = [Wallet(**w) for w in wallets])
async def update_user_extension(

View File

@ -1,7 +1,7 @@
import json
import hmac
import hashlib
from quart import url_for
from lnbits.helpers import url_for
from ecdsa import SECP256k1, SigningKey # type: ignore
from lnurl import encode as lnurl_encode # type: ignore
from typing import List, NamedTuple, Optional, Dict
@ -10,22 +10,6 @@ from pydantic import BaseModel
from lnbits.settings import WALLET
class User(BaseModel):
id: str
email: str
extensions: List[str] = []
wallets: List["Wallet"] = []
password: Optional[str] = None
@property
def wallet_ids(self) -> List[str]:
return [wallet.id for wallet in self.wallets]
def get_wallet(self, wallet_id: str) -> Optional["Wallet"]:
w = [wallet for wallet in self.wallets if wallet.id == wallet_id]
return w[0] if w else None
class Wallet(BaseModel):
id: str
name: str
@ -46,11 +30,12 @@ class Wallet(BaseModel):
@property
def lnurlwithdraw_full(self) -> str:
url = url_for(
"core.lnurl_full_withdraw",
"/withdraw",
external=True,
usr=self.user,
wal=self.id,
_external=True,
)
try:
return lnurl_encode(url)
@ -73,6 +58,22 @@ class Wallet(BaseModel):
return await get_wallet_payment(self.id, payment_hash)
class User(BaseModel):
id: str
email: Optional[str] = None
extensions: List[str] = []
wallets: List[Wallet] = []
password: Optional[str] = None
@property
def wallet_ids(self) -> List[str]:
return [wallet.id for wallet in self.wallets]
def get_wallet(self, wallet_id: str) -> Optional["Wallet"]:
w = [wallet for wallet in self.wallets if wallet.id == wallet_id]
return w[0] if w else None
class Payment(BaseModel):
checking_id: str
pending: bool
@ -83,10 +84,10 @@ class Payment(BaseModel):
bolt11: str
preimage: str
payment_hash: str
extra: Dict
extra: Optional[Dict] = {}
wallet_id: str
webhook: str
webhook_status: int
webhook: Optional[str]
webhook_status: Optional[int]
@classmethod
def from_row(cls, row: Row):

View File

@ -1,11 +1,10 @@
import trio
import asyncio
import json
import httpx
from io import BytesIO
from binascii import unhexlify
from typing import Optional, Tuple, Dict
from urllib.parse import urlparse, parse_qs
from quart import g, url_for
from lnurl import LnurlErrorResponse, decode as decode_lnurl # type: ignore
try:
@ -15,9 +14,10 @@ except ImportError: # pragma: nocover
from lnbits import bolt11
from lnbits.db import Connection
from lnbits.helpers import urlsafe_short_hash
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import WALLET
from lnbits.wallets.base import PaymentStatus, PaymentResponse
from lnbits.requestvars import g
from . import db
from .crud import (
@ -211,7 +211,7 @@ async def redeem_lnurl_withdraw(
return None
if wait_seconds:
await trio.sleep(wait_seconds)
await asyncio.sleep(wait_seconds)
params = {
"k1": res["k1"],
@ -220,10 +220,9 @@ async def redeem_lnurl_withdraw(
try:
params["balanceNotify"] = url_for(
"core.lnurl_balance_notify",
service=urlparse(lnurl_request).netloc,
f"/withdraw/notify/{urlparse(lnurl_request).netloc}",
external=True,
wal=wallet_id,
_external=True,
)
except Exception:
pass
@ -242,7 +241,7 @@ async def perform_lnurlauth(
cb = urlparse(callback)
k1 = unhexlify(parse_qs(cb.query)["k1"][0])
key = g.wallet.lnurlauth_key(cb.netloc)
key = g().wallet.lnurlauth_key(cb.netloc)
def int_to_bytes_suitable_der(x: int) -> bytes:
"""for strict DER we need to encode the integer with some quirks"""

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import httpx
from typing import List
@ -8,17 +8,19 @@ from . import db
from .crud import get_balance_notify
from .models import Payment
api_invoice_listeners: List[trio.MemorySendChannel] = []
api_invoice_listeners: List[asyncio.Queue] = []
async def register_listeners():
invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(5)
register_invoice_listener(invoice_paid_chan_send)
await wait_for_paid_invoices(invoice_paid_chan_recv)
async def register_task_listeners():
invoice_paid_queue = asyncio.Queue(5)
register_invoice_listener(invoice_paid_queue)
asyncio.create_task(wait_for_paid_invoices(invoice_paid_queue))
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
async for payment in invoice_paid_chan:
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
while True:
payment = await invoice_paid_queue.get()
# send information to sse channel
await dispatch_invoice_listener(payment)
@ -43,8 +45,8 @@ async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
async def dispatch_invoice_listener(payment: Payment):
for send_channel in api_invoice_listeners:
try:
send_channel.send_nowait(payment)
except trio.WouldBlock:
send_channel.put_nowait(payment)
except asyncio.QueueFull:
print("removing sse listener", send_channel)
api_invoice_listeners.remove(send_channel)

View File

@ -1,69 +1,60 @@
from fastapi.param_functions import Depends
from lnbits.auth_bearer import AuthBearer
from pydantic import BaseModel
import trio
import json
import httpx
import asyncio
import hashlib
from urllib.parse import urlparse, urlunparse, urlencode, parse_qs, ParseResult
from quart import current_app, make_response, url_for
from fastapi import Query
from http import HTTPStatus
import json
from binascii import unhexlify
from typing import Dict, List, Optional, Union
from http import HTTPStatus
from typing import Dict, Optional, Union
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
from fastapi import Query, Request
from fastapi.exceptions import HTTPException
from fastapi.param_functions import Depends
from fastapi.params import Body
from sse_starlette.sse import EventSourceResponse
from pydantic import BaseModel
from lnbits import bolt11, lnurl
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.utils.exchange_rates import currencies, fiat_amount_as_satoshis
from lnbits.core.models import Payment, Wallet
from lnbits.decorators import (WalletAdminKeyChecker, WalletInvoiceKeyChecker,
WalletTypeInfo, get_key_type)
from lnbits.helpers import url_for
from lnbits.requestvars import g
from lnbits.utils.exchange_rates import currencies, fiat_amount_as_satoshis
from .. import core_app, db
from ..crud import get_payments, save_balance_check, update_wallet
from ..services import (
PaymentFailure,
InvoiceFailure,
create_invoice,
pay_invoice,
perform_lnurlauth,
)
from ..services import (InvoiceFailure, PaymentFailure, create_invoice,
pay_invoice, perform_lnurlauth)
from ..tasks import api_invoice_listeners
@core_app.get(
"/api/v1/wallet",
# dependencies=[Depends(AuthBearer())]
)
# @api_check_wallet_key("invoice")
async def api_wallet():
@core_app.get("/api/v1/wallet")
async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
return (
{"id": g().wallet.id, "name": g().wallet.name, "balance": g().wallet.balance_msat},
{"id": wallet.wallet.id, "name": wallet.wallet.name, "balance": wallet.wallet.balance_msat},
HTTPStatus.OK,
)
@core_app.put("/api/v1/wallet/{new_name}")
@api_check_wallet_key("invoice")
async def api_update_wallet(new_name: str):
await update_wallet(g().wallet.id, new_name)
async def api_update_wallet(new_name: str, wallet: WalletTypeInfo = Depends(get_key_type)):
await update_wallet(wallet.wallet.id, new_name)
return (
{
"id": g().wallet.id,
"name": g().wallet.name,
"balance": g().wallet.balance_msat,
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"balance": wallet.wallet.balance_msat,
},
HTTPStatus.OK,
)
@core_app.get("/api/v1/payments")
@api_check_wallet_key("invoice")
async def api_payments():
return (
await get_payments(wallet_id=g().wallet.id, pending=True, complete=True),
HTTPStatus.OK,
)
async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)):
return await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True)
class CreateInvoiceData(BaseModel):
amount: int = Query(None, ge=1)
@ -75,9 +66,7 @@ class CreateInvoiceData(BaseModel):
extra: Optional[dict] = None
webhook: Optional[str] = None
@api_check_wallet_key("invoice")
# async def api_payments_create_invoice(amount: List[str] = Query([type: str = Query(None)])):
async def api_payments_create_invoice(data: CreateInvoiceData):
async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if "description_hash" in data:
description_hash = unhexlify(data.description_hash)
memo = ""
@ -94,7 +83,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData):
async with db.connect() as conn:
try:
payment_hash, payment_request = await create_invoice(
wallet_id=g().wallet.id,
wallet_id=wallet.id,
amount=amount,
memo=memo,
description_hash=description_hash,
@ -121,10 +110,9 @@ async def api_payments_create_invoice(data: CreateInvoiceData):
params={
"pr": payment_request,
"balanceNotify": url_for(
"core.lnurl_balance_notify",
service=urlparse(data.lnurl_callback).netloc,
f"/withdraw/notify/{urlparse(data.lnurl_callback).netloc}",
external=True,
wal=g().wallet.id,
_external=True,
),
},
timeout=10,
@ -152,10 +140,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData):
)
@api_check_wallet_key("admin")
async def api_payments_pay_invoice(
bolt11: str = Query(...), wallet: Optional[List[str]] = Query(None)
):
async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
try:
payment_hash = await pay_invoice(
wallet_id=wallet.id,
@ -180,11 +165,20 @@ async def api_payments_pay_invoice(
)
@core_app.post("/api/v1/payments")
async def api_payments_create(out: bool = True):
if out is True:
return await api_payments_pay_invoice()
return await api_payments_create_invoice()
@core_app.post("/api/v1/payments", deprecated=True,
description="DEPRECATED. Use /api/v2/TBD and /api/v2/TBD instead")
async def api_payments_create(wallet: WalletTypeInfo = Depends(get_key_type), out: bool = True,
invoiceData: Optional[CreateInvoiceData] = Body(None),
bolt11: Optional[str] = Query(None)):
if wallet.wallet_type < 0 or wallet.wallet_type > 2:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid")
if out is True and wallet.wallet_type == 0:
if not bolt11:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given")
return await api_payments_pay_invoice(bolt11, wallet.wallet) # admin key
return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key
class CreateLNURLData(BaseModel):
description_hash: str
@ -193,8 +187,7 @@ class CreateLNURLData(BaseModel):
comment: Optional[str] = None
description: Optional[str] = None
@core_app.post("/api/v1/payments/lnurl")
@api_check_wallet_key("admin")
@core_app.post("/api/v1/payments/lnurl", dependencies=[Depends(WalletAdminKeyChecker())])
async def api_payments_pay_lnurl(data: CreateLNURLData):
domain = urlparse(data.callback).netloc
@ -259,11 +252,46 @@ async def api_payments_pay_lnurl(data: CreateLNURLData):
HTTPStatus.CREATED,
)
async def subscribe(request: Request, wallet: Wallet):
this_wallet_id = wallet.wallet.id
payment_queue = asyncio.Queue(0)
print("adding sse listener", payment_queue)
api_invoice_listeners.append(payment_queue)
send_queue = asyncio.Queue(0)
async def payment_received() -> None:
while True:
payment: Payment = await payment_queue.get()
if payment.wallet_id == this_wallet_id:
await send_queue.put(("payment-received", payment))
asyncio.create_task(payment_received())
try:
while True:
typ, data = await send_queue.get()
message = [f"event: {typ}".encode("utf-8")]
if data:
jdata = json.dumps(dict(data.dict(), pending=False))
message.append(f"data: {jdata}".encode("utf-8"))
yield dict(data=jdata.encode("utf-8"), event=typ.encode("utf-8"))
except asyncio.CancelledError:
return
@core_app.get("/api/v1/payments/sse")
async def api_payments_sse(request: Request, wallet: WalletTypeInfo = Depends(get_key_type)):
return EventSourceResponse(subscribe(request, wallet))
@core_app.get("/api/v1/payments/{payment_hash}")
@api_check_wallet_key("invoice")
async def api_payment(payment_hash):
payment = await g().wallet.get_payment(payment_hash)
async def api_payment(payment_hash, wallet: WalletTypeInfo = Depends(get_key_type)):
payment = await wallet.wallet.get_payment(payment_hash)
if not payment:
return {"message": "Payment does not exist."}, HTTPStatus.NOT_FOUND
@ -280,62 +308,7 @@ async def api_payment(payment_hash):
HTTPStatus.OK,
)
@core_app.get("/api/v1/payments/sse")
@api_check_wallet_key("invoice", accept_querystring=True)
async def api_payments_sse():
this_wallet_id = g().wallet.id
send_payment, receive_payment = trio.open_memory_channel(0)
print("adding sse listener", send_payment)
api_invoice_listeners.append(send_payment)
send_event, event_to_send = trio.open_memory_channel(0)
async def payment_received() -> None:
async for payment in receive_payment:
if payment.wallet_id == this_wallet_id:
await send_event.send(("payment-received", payment))
async def repeat_keepalive():
await trio.sleep(1)
while True:
await send_event.send(("keepalive", ""))
await trio.sleep(25)
current_app.nursery.start_soon(payment_received)
current_app.nursery.start_soon(repeat_keepalive)
async def send_events():
try:
async for typ, data in event_to_send:
message = [f"event: {typ}".encode("utf-8")]
if data:
jdata = json.dumps(dict(data._asdict(), pending=False))
message.append(f"data: {jdata}".encode("utf-8"))
yield b"\n".join(message) + b"\r\n\r\n"
except trio.Cancelled:
return
response = await make_response(
send_events(),
{
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
"Transfer-Encoding": "chunked",
},
)
response.timeout = None
return response
@core_app.get("/api/v1/lnurlscan/{code}")
@api_check_wallet_key("invoice")
@core_app.get("/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())])
async def api_lnurlscan(code: str):
try:
url = lnurl.decode(code)
@ -444,8 +417,7 @@ async def api_lnurlscan(code: str):
return params
@core_app.post("/api/v1/lnurlauth")
@api_check_wallet_key("admin")
@core_app.post("/api/v1/lnurlauth", dependencies=[Depends(WalletAdminKeyChecker())])
async def api_perform_lnurlauth(callback: str):
err = await perform_lnurlauth(callback)
if err:
@ -453,6 +425,6 @@ async def api_perform_lnurlauth(callback: str):
return "", HTTPStatus.OK
@core_app.route("/api/v1/currencies", methods=["GET"])
@core_app.get("/api/v1/currencies")
async def api_list_currencies_available():
return list(currencies.keys())

View File

@ -1,51 +1,49 @@
from lnbits.requestvars import g
from os import path
import asyncio
from http import HTTPStatus
from typing import Optional
import jinja2
from fastapi import Request, status
from fastapi.exceptions import HTTPException
from fastapi.param_functions import Body
from fastapi.params import Depends, Query
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.routing import APIRouter
from pydantic.types import UUID4
from starlette.responses import HTMLResponse
from lnbits.core import core_app, db
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.settings import LNBITS_ALLOWED_USERS, SERVICE_FEE, LNBITS_SITE_TITLE
from lnbits.core import db
from lnbits.helpers import template_renderer, url_for
from lnbits.requestvars import g
from lnbits.settings import (LNBITS_ALLOWED_USERS, LNBITS_SITE_TITLE,
SERVICE_FEE)
from ..crud import (
create_account,
get_user,
update_user_extension,
create_wallet,
delete_wallet,
get_balance_check,
save_balance_notify,
)
from ..services import redeem_lnurl_withdraw, pay_invoice
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
from lnbits.jinja2_templating import Jinja2Templates
from ..crud import (create_account, create_wallet, delete_wallet,
get_balance_check, get_user, save_balance_notify,
update_user_extension)
from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
@core_app.get("/favicon.ico")
@core_html_routes.get("/favicon.ico")
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
@core_app.get("/", response_class=HTMLResponse)
@core_html_routes.get("/", response_class=HTMLResponse)
async def home(request: Request, lightning: str = None):
return g().templates.TemplateResponse("core/index.html", {"request": request, "lnurl": lightning})
return template_renderer().TemplateResponse("core/index.html", {"request": request, "lnurl": lightning})
@core_app.get("/extensions")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def extensions(enable: str, disable: str):
@core_html_routes.get("/extensions")
# @validate_uuids(["usr"], required=True)
# @check_user_exists()
async def extensions(request: Request, enable: str, disable: str):
extension_to_enable = enable
extension_to_disable = disable
if extension_to_enable and extension_to_disable:
abort(
HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension."
)
raise HTTPException(HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension.")
if extension_to_enable:
await update_user_extension(
@ -55,15 +53,16 @@ async def extensions(enable: str, disable: str):
await update_user_extension(
user_id=g.user.id, extension=extension_to_disable, active=False
)
return await templates.TemplateResponse("core/extensions.html", {"request": request, "user": get_user(g.user.id)})
return template_renderer().TemplateResponse("core/extensions.html", {"request": request, "user": get_user(g.user.id)})
@core_app.get("/wallet{usr}{wal}{nme}")
@core_html_routes.get("/wallet", response_class=HTMLResponse)
#Not sure how to validate
@validate_uuids(["usr", "wal"])
async def wallet(request: Request, usr: Optional[str], wal: Optional[str], nme: Optional[str]):
user_id = usr
wallet_id = wal
# @validate_uuids(["usr", "nme"])
async def wallet(request: Request = Query(None), nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None), wal: Optional[UUID4] = Query(None)):
user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None
wallet_name = nme
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
@ -78,32 +77,29 @@ async def wallet(request: Request, usr: Optional[str], wal: Optional[str], nme:
else:
user = await get_user(user_id)
if not user:
abort(HTTPStatus.NOT_FOUND, "User does not exist.")
return
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "User does not exist."})
if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
abort(HTTPStatus.UNAUTHORIZED, "User not authorized.")
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "User not authorized."})
if not wallet_id:
if user.wallets and not wallet_name:
wallet = user.wallets[0]
else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
return RedirectResponse(f"/wallet?usr={user.id}&wal={wallet.id}", status_code=status.HTTP_307_TEMPORARY_REDIRECT)
wallet = user.get_wallet(wallet_id)
if not wallet:
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "Wallet not found"})
return await templates.TemplateResponse(
"core/wallet.html", {"request":request,"user":user, "wallet":wallet, "service_fee":service_fee}
return template_renderer().TemplateResponse(
"core/wallet.html", {"request":request,"user":user.dict(), "wallet":wallet.dict(), "service_fee":service_fee}
)
@core_app.get("/withdraw")
@validate_uuids(["usr", "wal"], required=True)
async def lnurl_full_withdraw():
@core_html_routes.get("/withdraw")
# @validate_uuids(["usr", "wal"], required=True)
async def lnurl_full_withdraw(request: Request):
user = await get_user(request.args.get("usr"))
if not user:
return {"status": "ERROR", "reason": "User does not exist."}
@ -115,24 +111,22 @@ async def lnurl_full_withdraw():
return {
"tag": "withdrawRequest",
"callback": url_for(
"core.lnurl_full_withdraw_callback",
"/withdraw/cb",
external=True,
usr=user.id,
wal=wallet.id,
_external=True,
),
"k1": "0",
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
"maxWithdrawable": wallet.withdrawable_balance,
"defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}",
"balanceCheck": url_for(
"core.lnurl_full_withdraw", usr=user.id, wal=wallet.id, _external=True
),
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
}
@core_app.get("/withdraw/cb")
@validate_uuids(["usr", "wal"], required=True)
async def lnurl_full_withdraw_callback():
@core_html_routes.get("/withdraw/cb")
# @validate_uuids(["usr", "wal"], required=True)
async def lnurl_full_withdraw_callback(request: Request):
user = await get_user(request.args.get("usr"))
if not user:
return {"status": "ERROR", "reason": "User does not exist."}
@ -149,7 +143,7 @@ async def lnurl_full_withdraw_callback():
except:
pass
current_app.nursery.start_soon(pay)
asyncio.create_task(pay())
balance_notify = request.args.get("balanceNotify")
if balance_notify:
@ -158,53 +152,55 @@ async def lnurl_full_withdraw_callback():
return {"status": "OK"}
@core_app.get("/deletewallet")
@validate_uuids(["usr", "wal"], required=True)
@check_user_exists()
async def deletewallet():
wallet_id = request.args.get("wal", type=str)
user_wallet_ids = g.user.wallet_ids
@core_html_routes.get("/deletewallet")
# @validate_uuids(["usr", "wal"], required=True)
# @check_user_exists()
async def deletewallet(request: Request):
wallet_id = request.path_params.get("wal", type=str)
user_wallet_ids = g().user.wallet_ids
if wallet_id not in user_wallet_ids:
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
raise HTTPException(HTTPStatus.FORBIDDEN, "Not your wallet.")
else:
await delete_wallet(user_id=g.user.id, wallet_id=wallet_id)
await delete_wallet(user_id=g().user.id, wallet_id=wallet_id)
user_wallet_ids.remove(wallet_id)
if user_wallet_ids:
return redirect(url_for("core.wallet", usr=g.user.id, wal=user_wallet_ids[0]))
return RedirectResponse(url_for("/wallet", usr=g().user.id, wal=user_wallet_ids[0]),
status_code=status.HTTP_307_TEMPORARY_REDIRECT)
return redirect(url_for("core.home"))
return RedirectResponse(url_for("/"), status_code=status.HTTP_307_TEMPORARY_REDIRECT)
@core_app.get("/withdraw/notify/{service}")
@validate_uuids(["wal"], required=True)
async def lnurl_balance_notify(service: str):
@core_html_routes.get("/withdraw/notify/{service}")
# @validate_uuids(["wal"], required=True)
async def lnurl_balance_notify(request: Request, service: str):
bc = await get_balance_check(request.args.get("wal"), service)
if bc:
redeem_lnurl_withdraw(bc.wallet, bc.url)
@core_app.get("/lnurlwallet")
async def lnurlwallet():
@core_html_routes.get("/lnurlwallet")
async def lnurlwallet(request: Request):
async with db.connect() as conn:
account = await create_account(conn=conn)
user = await get_user(account.id, conn=conn)
wallet = await create_wallet(user_id=user.id, conn=conn)
current_app.nursery.start_soon(
redeem_lnurl_withdraw,
wallet.id,
request.args.get("lightning"),
"LNbits initial funding: voucher redeem.",
{"tag": "lnurlwallet"},
5, # wait 5 seconds before sending the invoice to the service
asyncio.create_task(
redeem_lnurl_withdraw(
wallet.id,
request.args.get("lightning"),
"LNbits initial funding: voucher redeem.",
{"tag": "lnurlwallet"},
5 # wait 5 seconds before sending the invoice to the service
)
)
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
return RedirectResponse(f"/wallet?usr={user.id}&wal={wallet.id}", status_code=status.HTTP_307_TEMPORARY_REDIRECT)
@core_app.get("/manifest/{usr}.webmanifest")
@core_html_routes.get("/manifest/{usr}.webmanifest")
async def manifest(usr: str):
user = await get_user(usr)
if not user:

View File

@ -1,7 +1,6 @@
import trio
import asyncio
import datetime
from http import HTTPStatus
from quart import jsonify
from lnbits import bolt11
@ -27,27 +26,27 @@ async def api_public_payment_longpolling(payment_hash):
except:
return {"message": "Invalid bolt11 invoice."}, HTTPStatus.BAD_REQUEST
send_payment, receive_payment = trio.open_memory_channel(0)
payment_queue = asyncio.Queue(0)
print("adding standalone invoice listener", payment_hash, send_payment)
api_invoice_listeners.append(send_payment)
print("adding standalone invoice listener", payment_hash, payment_queue)
api_invoice_listeners.append(payment_queue)
response = None
async def payment_info_receiver(cancel_scope):
async for payment in receive_payment:
async for payment in payment_queue.get():
if payment.payment_hash == payment_hash:
nonlocal response
response = ({"status": "paid"}, HTTPStatus.OK)
cancel_scope.cancel()
async def timeouter(cancel_scope):
await trio.sleep(45)
await asyncio.sleep(45)
cancel_scope.cancel()
async with trio.open_nursery() as nursery:
nursery.start_soon(payment_info_receiver, nursery.cancel_scope)
nursery.start_soon(timeouter, nursery.cancel_scope)
asyncio.create_task(payment_info_receiver())
asyncio.create_task(timeouter())
if response:
return response

View File

@ -1,12 +1,12 @@
import os
import trio
import asyncio
import time
import datetime
from typing import Optional
from contextlib import asynccontextmanager
from sqlalchemy import create_engine # type: ignore
from sqlalchemy_aio import TRIO_STRATEGY # type: ignore
from sqlalchemy_aio.base import AsyncConnection # type: ignore
from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
from .settings import LNBITS_DATA_FOLDER, LNBITS_DATABASE_URL
@ -132,8 +132,8 @@ class Database(Compat):
else:
self.schema = None
self.engine = create_engine(database_uri, strategy=TRIO_STRATEGY)
self.lock = trio.StrictFIFOLock()
self.engine = create_engine(database_uri, strategy=ASYNCIO_STRATEGY)
self.lock = asyncio.Lock()
@asynccontextmanager
async def connect(self):

View File

@ -1,36 +1,114 @@
from cerberus import Validator # type: ignore
from quart import g, abort, jsonify, request
from functools import wraps
from http import HTTPStatus
from fastapi.security import api_key
from lnbits.core.models import Wallet
from typing import List, Union
from uuid import UUID
from cerberus import Validator # type: ignore
from fastapi.exceptions import HTTPException
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.params import Security
from fastapi.security.api_key import APIKeyHeader, APIKeyQuery
from fastapi.security.base import SecurityBase
from starlette.requests import Request
from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.settings import LNBITS_ALLOWED_USERS
from lnbits.requestvars import g
from lnbits.settings import LNBITS_ALLOWED_USERS
def api_check_wallet_key(key_type: str = "invoice", accept_querystring=False):
def wrap(view):
@wraps(view)
async def wrapped_view(**kwargs):
try:
key_value = request.headers.get("X-Api-Key") or request.args["api-key"]
g().wallet = await get_wallet_for_key(key_value, key_type)
except KeyError:
return (
jsonify({"message": "`X-Api-Key` header missing."}),
HTTPStatus.BAD_REQUEST,
)
if not g().wallet:
return jsonify({"message": "Wrong keys."}), HTTPStatus.UNAUTHORIZED
class KeyChecker(SecurityBase):
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None):
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
self._key_type = "invoice"
self._api_key = api_key
if api_key:
self.model: APIKey= APIKey(
**{"in": APIKeyIn.query}, name="X-API-KEY", description="Wallet API Key - QUERY"
)
else:
self.model: APIKey= APIKey(
**{"in": APIKeyIn.header}, name="X-API-KEY", description="Wallet API Key - HEADER"
)
self.wallet = None
return await view(**kwargs)
async def __call__(self, request: Request) -> Wallet:
try:
key_value = self._api_key if self._api_key else request.headers.get("X-API-KEY") or request.query_params["api-key"]
# FIXME: Find another way to validate the key. A fetch from DB should be avoided here.
# Also, we should not return the wallet here - thats silly.
# Possibly store it in a Redis DB
self.wallet = await get_wallet_for_key(key_value, self._key_type)
if not self.wallet:
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Invalid key or expired key.")
return wrapped_view
except KeyError:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST,
detail="`X-API-KEY` header missing.")
return wrap
class WalletInvoiceKeyChecker(KeyChecker):
"""
WalletInvoiceKeyChecker will ensure that the provided invoice
wallet key is correct and populate g().wallet with the wallet
for the key in `X-API-key`.
The checker will raise an HTTPException when the key is wrong in some ways.
"""
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None):
super().__init__(scheme_name, auto_error, api_key)
self._key_type = "invoice"
class WalletAdminKeyChecker(KeyChecker):
"""
WalletAdminKeyChecker will ensure that the provided admin
wallet key is correct and populate g().wallet with the wallet
for the key in `X-API-key`.
The checker will raise an HTTPException when the key is wrong in some ways.
"""
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None):
super().__init__(scheme_name, auto_error, api_key)
self._key_type = "admin"
class WalletTypeInfo():
wallet_type: int
wallet: Wallet
def __init__(self, wallet_type: int, wallet: Wallet) -> None:
self.wallet_type = wallet_type
self.wallet = wallet
api_key_header = APIKeyHeader(name="X-API-KEY", auto_error=False, description="Admin or Invoice key for wallet API's")
api_key_query = APIKeyQuery(name="api-key", auto_error=False, description="Admin or Invoice key for wallet API's")
async def get_key_type(r: Request,
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query)) -> WalletTypeInfo:
# 0: admin
# 1: invoice
# 2: invalid
try:
checker = WalletAdminKeyChecker(api_key=api_key_query)
await checker.__call__(r)
return WalletTypeInfo(0, checker.wallet)
except HTTPException as e:
if e.status_code == HTTPStatus.UNAUTHORIZED:
pass
except:
raise
try:
checker = WalletInvoiceKeyChecker()
await checker.__call__(r)
return WalletTypeInfo(1, checker.wallet)
except HTTPException as e:
if e.status_code == HTTPStatus.UNAUTHORIZED:
return WalletTypeInfo(2, None)
except:
raise
def api_validate_post_request(*, schema: dict):
def wrap(view):
@ -77,28 +155,4 @@ def check_user_exists(param: str = "usr"):
return wrap
def validate_uuids(
params: List[str], *, required: Union[bool, List[str]] = False, version: int = 4
):
def wrap(view):
@wraps(view)
async def wrapped_view(**kwargs):
query_params = {
param: request.args.get(param, type=str) for param in params
}
for param, value in query_params.items():
if not value and (required is True or (required and param in required)):
abort(HTTPStatus.BAD_REQUEST, f"`{param}` is required.")
if value:
try:
UUID(value, version=version)
except ValueError:
abort(HTTPStatus.BAD_REQUEST, f"`{param}` is not a valid UUID.")
return await view(**kwargs)
return wrapped_view
return wrap

View File

@ -1,5 +1,4 @@
import hashlib
from quart import jsonify, url_for, request
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
from lnbits.core.services import create_invoice

View File

@ -2,7 +2,7 @@ import json
import base64
import hashlib
from collections import OrderedDict
from quart import url_for
from typing import Optional, List, Dict
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore

View File

@ -1,9 +1,8 @@
import time
from datetime import datetime
from quart import g, render_template, request
from http import HTTPStatus
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.decorators import check_user_exists
from lnbits.core.models import Payment
from lnbits.core.crud import get_standalone_payment
@ -15,8 +14,8 @@ from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
@offlineshop_ext.get("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
# @validate_uuids(["usr"], required=True)
# @check_user_exists()
async def index(request: Request):
return await templates.TemplateResponse("offlineshop/index.html", {"request": request,"user":g.user})

View File

@ -1,11 +1,12 @@
from typing import Optional
from pydantic.main import BaseModel
from quart import g, jsonify
from http import HTTPStatus
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.utils.exchange_rates import currencies
from lnbits.requestvars import g
from . import offlineshop_ext
from .crud import (
@ -27,7 +28,7 @@ async def api_list_currencies_available():
@offlineshop_ext.get("/api/v1/offlineshop")
@api_check_wallet_key("invoice")
async def api_shop_from_wallet():
shop = await get_or_create_shop_by_wallet(g.wallet.id)
shop = await get_or_create_shop_by_wallet(g().wallet.id)
items = await get_items(shop.id)
try:
@ -60,7 +61,7 @@ class CreateItemsData(BaseModel):
@offlineshop_ext.put("/api/v1/offlineshop/items/{item_id}")
@api_check_wallet_key("invoice")
async def api_add_or_update_item(data: CreateItemsData, item_id=None):
shop = await get_or_create_shop_by_wallet(g.wallet.id)
shop = await get_or_create_shop_by_wallet(g().wallet.id)
if item_id == None:
await add_item(
shop.id,
@ -87,7 +88,7 @@ async def api_add_or_update_item(data: CreateItemsData, item_id=None):
@offlineshop_ext.delete("/api/v1/offlineshop/items/{item_id}")
@api_check_wallet_key("invoice")
async def api_delete_item(item_id):
shop = await get_or_create_shop_by_wallet(g.wallet.id)
shop = await get_or_create_shop_by_wallet(g().wallet.id)
await delete_item_from_shop(shop.id, item_id)
return "", HTTPStatus.NO_CONTENT
@ -104,7 +105,7 @@ async def api_set_method(data: CreateMethodData):
wordlist = data.wordlist.split("\n") if data.wordlist else None
wordlist = [word.strip() for word in wordlist if word.strip()]
shop = await get_or_create_shop_by_wallet(g.wallet.id)
shop = await get_or_create_shop_by_wallet(g().wallet.id)
if not shop:
return "", HTTPStatus.NOT_FOUND

View File

@ -6,31 +6,34 @@ from lnbits.decorators import check_user_exists, validate_uuids
from . import withdraw_ext
from .crud import get_withdraw_link, chunks
from fastapi import FastAPI, Request
from fastapi import FastAPI, Request, Response
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
@withdraw_ext.get("/")
@withdraw_ext.get("/", status_code=HTTPStatus.OK)
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index(request: Request):
return await templates.TemplateResponse("withdraw/index.html", {"request":request,"user":g.user})
@withdraw_ext.get("/<link_id>")
async def display(request: Request, link_id):
link = await get_withdraw_link(link_id, 0) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
@withdraw_ext.get("/{link_id}", status_code=HTTPStatus.OK)
async def display(request: Request, link_id, response: Response):
link = await get_withdraw_link(link_id, 0)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist." #probably here is where we should return the 404??
return await templates.TemplateResponse("withdraw/display.html", {"request":request,"link":link, "unique":True})
@withdraw_ext.get("/img/<link_id>")
async def img(request: Request, link_id):
link = await get_withdraw_link(link_id, 0) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
@withdraw_ext.get("/img/{link_id}", status_code=HTTPStatus.OK)
async def img(request: Request, link_id, response: Response):
link = await get_withdraw_link(link_id, 0)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
qr = pyqrcode.create(link.lnurl)
stream = BytesIO()
qr.svg(stream, scale=3)
@ -46,19 +49,21 @@ async def img(request: Request, link_id):
)
@withdraw_ext.get("/print/<link_id>")
async def print_qr(request: Request, link_id):
link = await get_withdraw_link(link_id) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
@withdraw_ext.get("/print/{link_id}", status_code=HTTPStatus.OK)
async def print_qr(request: Request, link_id, response: Response):
link = await get_withdraw_link(link_id)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
if link.uses == 0:
return await templates.TemplateResponse("withdraw/print_qr.html", {"request":request,link:link, unique:False})
links = []
count = 0
for x in link.usescsv.split(","):
linkk = await get_withdraw_link(link_id, count) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
linkk = await get_withdraw_link(link_id, count)
if not linkk:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
links.append(str(linkk.lnurl))
count = count + 1
page_link = list(chunks(links, 2))

View File

@ -5,7 +5,7 @@ from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from pydantic import BaseModel
from fastapi import FastAPI, Query
from fastapi import FastAPI, Query, Response
from . import withdraw_ext
from .crud import (
@ -19,47 +19,41 @@ from .crud import (
)
@withdraw_ext.get("/api/v1/links")
@withdraw_ext.get("/api/v1/links", status_code=200)
@api_check_wallet_key("invoice")
async def api_links():
async def api_links(response: Response):
wallet_ids = [g.wallet.id]
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
try:
return (
[
return [
{
**link._asdict(),
**{"lnurl": link.lnurl},
}
for link in await get_withdraw_links(wallet_ids)
],
HTTPStatus.OK,
)
]
except LnurlInvalidUrl:
return (
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
},
HTTPStatus.UPGRADE_REQUIRED,
)
response.status_code = HTTPStatus.UPGRADE_REQUIRED
return { "message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor." }
@withdraw_ext.get("/api/v1/links/{link_id}")
@withdraw_ext.get("/api/v1/links/{link_id}", status_code=200)
@api_check_wallet_key("invoice")
async def api_link_retrieve(link_id):
async def api_link_retrieve(link_id, response: Response):
link = await get_withdraw_link(link_id, 0)
if not link:
return ({"message": "Withdraw link does not exist."},
HTTPStatus.NOT_FOUND,
)
response.status_code = HTTPStatus.NOT_FOUND
return {"message": "Withdraw link does not exist."}
if link.wallet != g.wallet.id:
return {"message": "Not your withdraw link."}, HTTPStatus.FORBIDDEN
response.status_code = HTTPStatus.FORBIDDEN
return {"message": "Not your withdraw link."}
return {**link, **{"lnurl": link.lnurl}}, HTTPStatus.OK
return {**link, **{"lnurl": link.lnurl}}
class CreateData(BaseModel):
title: str = Query(...)
@ -69,17 +63,15 @@ class CreateData(BaseModel):
wait_time: int = Query(..., ge=1)
is_unique: bool
@withdraw_ext.post("/api/v1/links")
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@withdraw_ext.put("/api/v1/links/{link_id}")
@api_check_wallet_key("admin")
async def api_link_create_or_update(data: CreateData, link_id: str = None):
async def api_link_create_or_update(data: CreateData, link_id: str = None, response: Response):
if data.max_withdrawable < data.min_withdrawable:
return (
{
response.status_code = HTTPStatus.BAD_REQUEST
return {
"message": "`max_withdrawable` needs to be at least `min_withdrawable`."
},
HTTPStatus.BAD_REQUEST,
)
}
usescsv = ""
for i in range(data.uses):
@ -92,43 +84,41 @@ async def api_link_create_or_update(data: CreateData, link_id: str = None):
if link_id:
link = await get_withdraw_link(link_id, 0)
if not link:
return (
jsonify({"message": "Withdraw link does not exist."}),
HTTPStatus.NOT_FOUND,
)
response.status_code = HTTPStatus.NOT_FOUND
return {"message": "Withdraw link does not exist."}
if link.wallet != g.wallet.id:
return {"message": "Not your withdraw link."}, HTTPStatus.FORBIDDEN
response.status_code = HTTPStatus.FORBIDDEN
return {"message": "Not your withdraw link."}
link = await update_withdraw_link(link_id, **data, usescsv=usescsv, used=0)
else:
link = await create_withdraw_link(
wallet_id=g.wallet.id, **data, usescsv=usescsv
)
return ({**link, **{"lnurl": link.lnurl}},
HTTPStatus.OK if link_id else HTTPStatus.CREATED,
)
if link_id:
response.status_code = HTTPStatus.OK
return {**link, **{"lnurl": link.lnurl}}
@withdraw_ext.delete("/api/v1/links/{link_id}")
@withdraw_ext.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.NO_CONTENT)
@api_check_wallet_key("admin")
async def api_link_delete(link_id):
async def api_link_delete(link_id, response: Response):
link = await get_withdraw_link(link_id)
if not link:
return ({"message": "Withdraw link does not exist."},
HTTPStatus.NOT_FOUND,
)
response.status_code = HTTPStatus.NOT_FOUND
return {"message": "Withdraw link does not exist."}
if link.wallet != g.wallet.id:
return {"message": "Not your withdraw link."}, HTTPStatus.FORBIDDEN
response.status_code = HTTPStatus.FORBIDDEN
return {"message": "Not your withdraw link."}
await delete_withdraw_link(link_id)
return "", HTTPStatus.NO_CONTENT
return ""
@withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}")
@withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK)
@api_check_wallet_key("invoice")
async def api_hash_retrieve(the_hash, lnurl_id):
hashCheck = await get_hash_check(the_hash, lnurl_id)
return hashCheck, HTTPStatus.OK
return hashCheck

View File

@ -1,11 +1,15 @@
import glob
import json
import os
import glob
from typing import Any, List, NamedTuple, Optional
import jinja2
import shortuuid # type: ignore
from typing import List, NamedTuple, Optional
from lnbits.jinja2_templating import Jinja2Templates
from lnbits.requestvars import g
from .settings import LNBITS_DISABLED_EXTENSIONS, LNBITS_PATH
import lnbits.settings as settings
class Extension(NamedTuple):
@ -20,9 +24,9 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
self._disabled: List[str] = LNBITS_DISABLED_EXTENSIONS
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._extension_folders: List[str] = [
x[1] for x in os.walk(os.path.join(LNBITS_PATH, "extensions"))
x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions"))
][0]
@property
@ -37,7 +41,7 @@ class ExtensionManager:
]:
try:
with open(
os.path.join(LNBITS_PATH, "extensions", extension, "config.json")
os.path.join(settings.LNBITS_PATH, "extensions", extension, "config.json")
) as json_file:
config = json.load(json_file)
is_valid = True
@ -105,7 +109,7 @@ def get_css_vendored(prefer_minified: bool = False) -> List[str]:
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
paths: List[str] = []
for path in glob.glob(
os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True
os.path.join(settings.LNBITS_PATH, "static/vendor/**"), recursive=True
):
if path.endswith(".min" + ext):
# path is minified
@ -131,4 +135,36 @@ def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
def url_for_vendored(abspath: str) -> str:
return "/" + os.path.relpath(abspath, LNBITS_PATH)
return "/" + os.path.relpath(abspath, settings.LNBITS_PATH)
def url_for(
endpoint: str,
external: Optional[bool] = False,
**params: Any,
) -> str:
base = g().base_url if external else ""
url_params = "?"
for key in params:
url_params += f"{key}={params[key]}&"
url = f"{base}{endpoint}{url_params}"
return url
def template_renderer() -> Jinja2Templates:
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(["lnbits/templates", "lnbits/core/templates"]),
)
t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE
t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE
t.env.globals["SITE_DESCRIPTION"] = settings.LNBITS_SITE_DESCRIPTION
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.LNBITS_THEME_OPTIONS
t.env.globals["LNBITS_VERSION"] = settings.LNBITS_COMMIT
t.env.globals["EXTENSIONS"] = get_valid_extensions()
if settings.DEBUG:
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())
else:
t.env.globals["VENDORED_JS"] = ["/static/bundle.js"]
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
return t

View File

@ -135,7 +135,7 @@ window.LNbits = {
return obj
},
user: function (data) {
var obj = _.object(['id', 'email', 'extensions', 'wallets'], data)
var obj = {id: data.id, email: data.email, extensions: data.extensions, wallets: data.wallets}
var mapWallet = this.wallet
obj.wallets = obj.wallets
.map(function (obj) {
@ -153,35 +153,30 @@ window.LNbits = {
return obj
},
wallet: function (data) {
var obj = _.object(
['id', 'name', 'user', 'adminkey', 'inkey', 'balance'],
data
)
obj.msat = obj.balance
obj.sat = Math.round(obj.balance / 1000)
obj.fsat = new Intl.NumberFormat(window.LOCALE).format(obj.sat)
obj.url = ['/wallet?usr=', obj.user, '&wal=', obj.id].join('')
return obj
newWallet = {id: data.id, name: data.name, adminkey: data.adminkey, inkey: data.inkey}
newWallet.msat = data.balance_msat
newWallet.sat = Math.round(data.balance_msat / 1000)
newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format(newWallet.sat)
newWallet.url = ['/wallet?usr=', data.user, '&wal=', data.id].join('')
return newWallet
},
payment: function (data) {
var obj = _.object(
[
'checking_id',
'pending',
'amount',
'fee',
'memo',
'time',
'bolt11',
'preimage',
'payment_hash',
'extra',
'wallet_id',
'webhook',
'webhook_status'
],
data
)
obj = {
checking_id:data.id,
pending: data.pending,
amount: data.amount,
fee: data.fee,
memo: data.memo,
time: data.time,
bolt11: data.bolt11,
preimage: data.preimage,
payment_hash: data.payment_hash,
extra: data.extra,
wallet_id: data.wallet_id,
webhook: data.webhook,
webhook_status: data.webhook_status,
}
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'

View File

@ -1,8 +1,7 @@
import time
import trio
import asyncio
import traceback
from http import HTTPStatus
from quart import current_app
from typing import List, Callable
from lnbits.settings import WALLET
@ -25,21 +24,21 @@ def record_async(func: Callable) -> Callable:
return recorder
def run_deferred_async():
async def run_deferred_async():
for func in deferred_async:
current_app.nursery.start_soon(catch_everything_and_restart, func)
asyncio.create_task(catch_everything_and_restart(func))
async def catch_everything_and_restart(func):
try:
await func()
except trio.Cancelled:
except asyncio.CancelledError:
raise # because we must pass this up
except Exception as exc:
print("caught exception in background task:", exc)
print(traceback.format_exc())
print("will restart the task in 5 seconds.")
await trio.sleep(5)
await asyncio.sleep(5)
await catch_everything_and_restart(func)
@ -47,10 +46,10 @@ async def send_push_promise(a, b) -> None:
pass
invoice_listeners: List[trio.MemorySendChannel] = []
invoice_listeners: List[asyncio.Queue] = []
def register_invoice_listener(send_chan: trio.MemorySendChannel):
def register_invoice_listener(send_chan: asyncio.Queue):
"""
A method intended for extensions to call when they want to be notified about
new invoice payments incoming.
@ -65,18 +64,19 @@ async def webhook_handler():
return "", HTTPStatus.NO_CONTENT
internal_invoice_paid, internal_invoice_received = trio.open_memory_channel(0)
internal_invoice_queue = asyncio.Queue(0)
async def internal_invoice_listener():
async for checking_id in internal_invoice_received:
current_app.nursery.start_soon(invoice_callback_dispatcher, checking_id)
while True:
checking_id = await internal_invoice_queue.get()
asyncio.create_task(invoice_callback_dispatcher(checking_id))
async def invoice_listener():
async for checking_id in WALLET.paid_invoices_stream():
print("> got a payment notification", checking_id)
current_app.nursery.start_soon(invoice_callback_dispatcher, checking_id)
asyncio.create_task(invoice_callback_dispatcher(checking_id))
async def check_pending_payments():
@ -100,7 +100,7 @@ async def check_pending_payments():
# that will be handled by the global invoice listeners, hopefully
incoming = False
await trio.sleep(60 * 30) # every 30 minutes
await asyncio.sleep(60 * 30) # every 30 minutes
async def perform_balance_checks():
@ -108,7 +108,7 @@ async def perform_balance_checks():
for bc in await get_balance_checks():
redeem_lnurl_withdraw(bc.wallet, bc.url)
await trio.sleep(60 * 60 * 6) # every 6 hours
await asyncio.sleep(60 * 60 * 6) # every 6 hours
async def invoice_callback_dispatcher(checking_id: str):
@ -116,4 +116,4 @@ async def invoice_callback_dispatcher(checking_id: str):
if payment and payment.is_in:
await payment.set_pending(False)
for send_chan in invoice_listeners:
await send_chan.send(payment)
await send_chan.put(payment)

View File

@ -0,0 +1,36 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<center>
<h3 class="q-my-none">Error</h3>
<br />
<q-icon
name="warning"
class="text-grey"
style="font-size: 20rem"
></q-icon>
<h5 class="q-my-none">{{ err }}</h5>
<h4>If you believe this shouldn't be an error please bring it up on https://t.me/lnbits</h4>
<br />
</center>
</q-card-section>
</q-card>
</div>
{% endblock %} {% block scripts %}
<script>
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {}
}
})
</script>
{% endblock %}
</div>

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import httpx
from typing import Callable, NamedTuple
@ -219,12 +219,12 @@ async def btc_price(currency: str) -> float:
"to": currency.lower(),
}
rates = []
send_channel, receive_channel = trio.open_memory_channel(0)
send_channel = asyncio.Queue(0)
async def controller(nursery):
failures = 0
while True:
rate = await receive_channel.receive()
rate = await send_channel.get()
if rate:
rates.append(rate)
else:
@ -248,10 +248,9 @@ async def btc_price(currency: str) -> float:
except Exception:
await send_channel.send(None)
async with trio.open_nursery() as nursery:
nursery.start_soon(controller, nursery)
for key, provider in exchange_rate_providers.items():
nursery.start_soon(fetch_price, key, provider)
# asyncio.create_task(controller, nursery)
for key, provider in exchange_rate_providers.items():
asyncio.create_task(fetch_price(key, provider))
if not rates:
return 9999999999

View File

@ -3,7 +3,7 @@ try:
except ImportError: # pragma: nocover
LightningRpc = None
import trio
import asyncio
import random
import json
@ -116,7 +116,7 @@ class CLightningWallet(Wallet):
raise KeyError("supplied an invalid checking_id")
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
stream = await trio.open_unix_socket(self.rpc)
stream = await asyncio.open_unix_socket(self.rpc)
i = 0
while True:

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import json
import httpx
from os import getenv
@ -146,4 +146,4 @@ class LNbitsWallet(Wallet):
pass
print("lost connection to lnbits /payments/sse, retrying in 5 seconds")
await trio.sleep(5)
await asyncio.sleep(5)

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import httpx
import json
import base64
@ -183,4 +183,4 @@ class LndRestWallet(Wallet):
pass
print("lost connection to lnd invoices stream, retrying in 5 seconds")
await trio.sleep(5)
await asyncio.sleep(5)

View File

@ -1,10 +1,9 @@
import json
import trio
import asyncio
import httpx
from os import getenv
from http import HTTPStatus
from typing import Optional, Dict, AsyncGenerator
from quart import request
from .base import (
StatusResponse,
@ -117,8 +116,9 @@ class LNPayWallet(Wallet):
return PaymentStatus(statuses[r.json()["settled"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.send, receive = trio.open_memory_channel(0)
async for value in receive:
self.queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value
async def webhook_listener(self):
@ -143,6 +143,6 @@ class LNPayWallet(Wallet):
)
data = r.json()
if data["settled"]:
await self.send.send(lntx_id)
await self.queue.put(lntx_id)
return "", HTTPStatus.NO_CONTENT

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import json
import httpx
from os import getenv
@ -150,4 +150,4 @@ class LntxbotWallet(Wallet):
pass
print("lost connection to lntxbot /payments/stream, retrying in 5 seconds")
await trio.sleep(5)
await asyncio.sleep(5)

View File

@ -1,10 +1,10 @@
import trio
import asyncio
from lnbits.helpers import url_for
import hmac
import httpx
from http import HTTPStatus
from os import getenv
from typing import Optional, AsyncGenerator
from quart import request, url_for
from .base import (
StatusResponse,
@ -63,7 +63,7 @@ class OpenNodeWallet(Wallet):
json={
"amount": amount,
"description": memo or "",
"callback_url": url_for("webhook_listener", _external=True),
"callback_url": url_for("/webhook_listener", _external=True),
},
timeout=40,
)
@ -125,8 +125,9 @@ class OpenNodeWallet(Wallet):
return PaymentStatus(statuses[r.json()["data"]["status"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.send, receive = trio.open_memory_channel(0)
async for value in receive:
self.queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value
async def webhook_listener(self):
@ -141,5 +142,5 @@ class OpenNodeWallet(Wallet):
print("invalid webhook, not from opennode")
return "", HTTPStatus.NO_CONTENT
await self.send.send(charge_id)
await self.queue.put(charge_id)
return "", HTTPStatus.NO_CONTENT

View File

@ -1,4 +1,4 @@
import trio
import asyncio
import json
import httpx
import random
@ -199,4 +199,4 @@ class SparkWallet(Wallet):
pass
print("lost connection to spark /stream, retrying in 5 seconds")
await trio.sleep(5)
await asyncio.sleep(5)