Compare commits

..

205 Commits
v3.1.0 ... dev

Author SHA1 Message Date
37d2d4e215 Merge pull request 'Update dependency tox to v4.29.0' (#243) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 56s
Reviewed-on: #243
2025-08-30 18:22:25 +03:00
4601a7afa2 Update dependency tox to v4.29.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 59s
Tests / Build and Test (3.11) (pull_request) Successful in 1m16s
Tests / Build and Test (3.12) (pull_request) Successful in 1m15s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
2025-08-30 02:33:55 +03:00
2bcc1f94f0 Update dependency typing-extensions to ~=4.15.0 (#242)
All checks were successful
Analysis / SonarCloud (push) Successful in 57s
2025-08-25 20:06:36 +03:00
f0532f9d39 Update dependency typing-extensions to ~=4.15.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 53s
Tests / Build and Test (3.11) (pull_request) Successful in 1m16s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
2025-08-25 17:11:20 +03:00
97ac53b597 Update dependency types-aiofiles to v24.1.0.20250822 (#241)
All checks were successful
Analysis / SonarCloud (push) Successful in 1m10s
2025-08-22 18:13:26 +03:00
e94baabcfe Update dependency types-aiofiles to v24.1.0.20250822
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m31s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
2025-08-22 17:59:28 +03:00
c3b8eca06f Merge pull request 'Update dependency ujson to ~=5.11.0' (#238) from renovate/ujson-5.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 52s
Reviewed-on: #238
2025-08-22 16:41:59 +03:00
d207c564dc Merge pull request 'Update dependency types-ujson to v5.10.0.20250822' (#240) from renovate/types-ujson-5.x into dev
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
Reviewed-on: #240
2025-08-22 16:41:15 +03:00
e4807e2999 Update dependency types-ujson to v5.10.0.20250822
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 48s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m10s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
2025-08-22 06:37:11 +03:00
540c94c0ed Update dependency ujson to ~=5.11.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m53s
Tests / Build and Test (3.11) (pull_request) Successful in 1m26s
Tests / Build and Test (3.12) (pull_request) Successful in 1m25s
Tests / Build and Test (3.13) (pull_request) Successful in 1m22s
2025-08-20 15:21:10 +03:00
a10c4a8f51 Merge pull request 'Update dependency types-aiofiles to v24.1.0.20250809' (#236) from renovate/types-aiofiles-24.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 55s
Reviewed-on: #236
2025-08-12 15:44:04 +03:00
9e40f89d67 Merge pull request 'Update dependency pylint to v3.3.8' (#237) from renovate/pylint-3.x into dev
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
Reviewed-on: #237
2025-08-12 15:43:53 +03:00
f5db3cb529 Update dependency pylint to v3.3.8
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 44s
Tests / Build and Test (3.11) (pull_request) Successful in 1m21s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m13s
2025-08-09 13:13:34 +03:00
eca59e4673 Update dependency types-aiofiles to v24.1.0.20250809
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 43s
Tests / Build and Test (3.11) (pull_request) Successful in 1m20s
Tests / Build and Test (3.12) (pull_request) Successful in 1m19s
Tests / Build and Test (3.13) (pull_request) Successful in 1m11s
2025-08-09 07:02:37 +03:00
923bad856b Merge pull request 'Update dependency redis to ~=6.4.0' (#235) from renovate/redis-6.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 56s
Reviewed-on: #235
2025-08-07 12:10:17 +03:00
902ccb5947 Update dependency redis to ~=6.4.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 57s
Tests / Build and Test (3.11) (pull_request) Successful in 1m24s
Tests / Build and Test (3.12) (pull_request) Successful in 1m22s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
2025-08-07 11:48:03 +03:00
1fea7ea5f8 Merge pull request 'Update dependency redis to ~=6.3.0' (#234) from renovate/redis-6.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 50s
Reviewed-on: #234
2025-08-05 11:50:40 +03:00
37928baa6f Update dependency redis to ~=6.3.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 50s
Tests / Build and Test (3.11) (pull_request) Successful in 1m24s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
2025-08-05 11:25:32 +03:00
1f28b1771a Merge pull request 'Update dependency build to v1.3.0' (#233) from renovate/build-1.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 1m2s
Reviewed-on: #233
2025-08-02 02:07:53 +03:00
5b677f6741 Update dependency build to v1.3.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m8s
Tests / Build and Test (3.11) (pull_request) Successful in 1m38s
Tests / Build and Test (3.12) (pull_request) Successful in 1m39s
Tests / Build and Test (3.13) (pull_request) Successful in 1m31s
2025-08-02 01:06:48 +03:00
883cd50fb3 Merge pull request 'Update dependency types-aiofiles to v24.1.0.20250801' (#232) from renovate/types-aiofiles-24.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 54s
Reviewed-on: #232
2025-08-01 10:11:09 +03:00
403b52042e Update dependency types-aiofiles to v24.1.0.20250801
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 43s
Tests / Build and Test (3.11) (pull_request) Successful in 1m21s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
2025-08-01 07:37:03 +03:00
5ca813b095 Merge pull request 'Update dependency tox to v4.28.4' (#231) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 52s
Reviewed-on: #231
2025-08-01 00:53:00 +03:00
b8cd7f2316 Update dependency tox to v4.28.4
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 47s
Tests / Build and Test (3.11) (pull_request) Successful in 1m20s
Tests / Build and Test (3.12) (pull_request) Successful in 1m19s
Tests / Build and Test (3.13) (pull_request) Successful in 1m10s
2025-08-01 00:24:04 +03:00
454c80ad6b Merge pull request 'Update dependency mypy to v1.17.1' (#230) from renovate/mypy-1.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 54s
Reviewed-on: #230
2025-07-31 14:34:47 +03:00
1bbab5c154 Update dependency mypy to v1.17.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 46s
Tests / Build and Test (3.11) (pull_request) Successful in 1m20s
Tests / Build and Test (3.12) (pull_request) Successful in 1m19s
Tests / Build and Test (3.13) (pull_request) Successful in 1m14s
2025-07-31 10:59:00 +03:00
6cb7f9b2f3 Merge pull request 'Update dependency tox to v4.28.3' (#229) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 52s
Reviewed-on: #229
2025-07-26 12:32:43 +03:00
a4c797079e Update dependency tox to v4.28.3
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 51s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m16s
Tests / Build and Test (3.13) (pull_request) Successful in 1m10s
2025-07-26 03:50:04 +03:00
9a9edbc1a8 Merge pull request 'Update dependency tox to v4.28.1' (#228) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 52s
Reviewed-on: #228
2025-07-23 17:41:14 +03:00
f991d86d4a Update dependency tox to v4.28.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 47s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m13s
Tests / Build and Test (3.13) (pull_request) Successful in 1m10s
2025-07-23 10:07:52 +03:00
cea1b087d3 Merge pull request 'Update dependency tox to v4.28.0' (#227) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 53s
Reviewed-on: #227
2025-07-21 00:52:28 +03:00
19399fe0ac Update dependency tox to v4.28.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 44s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m8s
2025-07-20 21:36:44 +03:00
6da6812d1d Merge pull request 'Update dependency pytest-asyncio to v1.1.0' (#226) from renovate/pytest-asyncio-1.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 1m2s
Reviewed-on: #226
2025-07-16 17:29:52 +03:00
ec3dc3a42a Update dependency pytest-asyncio to v1.1.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 41s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
2025-07-16 07:53:38 +03:00
01dc9f5d87 Merge pull request 'Update dependency mypy to v1.17.0' (#225) from renovate/mypy-1.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 55s
Reviewed-on: #225
2025-07-15 03:09:26 +03:00
aa38fc5f0f Update dependency mypy to v1.17.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 52s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m15s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
2025-07-15 00:05:40 +03:00
097581bac7 Bump version to 4.4.0
All checks were successful
Analysis / SonarCloud (push) Successful in 50s
Analysis / SonarCloud (pull_request) Successful in 47s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
2025-07-09 14:43:57 +02:00
b8bbba66ec Fixed default_ttl_seconds being passed in a wrong call
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-07-09 14:43:20 +02:00
ef7380ae45 WIP: Documentation improvement and format change to Google
Some checks failed
Analysis / SonarCloud (push) Successful in 54s
Analysis / SonarCloud (pull_request) Successful in 48s
Tests / Build and Test (3.11) (pull_request) Failing after 1m2s
Tests / Build and Test (3.12) (pull_request) Failing after 1m0s
Tests / Build and Test (3.13) (pull_request) Failing after 59s
2025-07-09 14:32:50 +02:00
727d531d63 Added support for default_ttl_seconds in create_cache_client() 2025-07-09 14:31:55 +02:00
7d95b1efee Merge pull request 'Update dependency types-aiofiles to v24.1.0.20250708' (#222) from renovate/types-aiofiles-24.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 57s
Reviewed-on: #222
2025-07-08 15:45:53 +03:00
03115b4059 Update dependency types-aiofiles to v24.1.0.20250708
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 44s
Tests / Build and Test (3.11) (pull_request) Successful in 1m12s
Tests / Build and Test (3.12) (pull_request) Successful in 1m13s
Tests / Build and Test (3.13) (pull_request) Successful in 1m7s
2025-07-08 06:48:18 +03:00
e4ce5976f2 Changed default cache TTL to 0 and None for memcached and redis respectively
All checks were successful
Analysis / SonarCloud (push) Successful in 50s
Analysis / SonarCloud (pull_request) Successful in 52s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m18s
2025-07-08 00:01:14 +02:00
32a9e14d0c Added a default TTL of 300 seconds for cache entries (#219)
All checks were successful
Analysis / SonarCloud (push) Successful in 50s
Analysis / SonarCloud (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m15s
Tests / Build and Test (3.13) (pull_request) Successful in 1m19s
2025-07-07 23:47:55 +02:00
3110bb64b1 Closes #219
All checks were successful
Analysis / SonarCloud (push) Successful in 1m35s
2025-07-07 23:35:21 +02:00
ad38dbdca1 Update dependency pytest to v8.4.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 50s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m15s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
Analysis / SonarCloud (push) Successful in 50s
2025-06-18 09:21:02 +03:00
edc3e0717d Update dependency tox to v4.27.0
All checks were successful
Tests / Build and Test (3.11) (pull_request) Successful in 1m22s
Tests / Build and Test (3.12) (pull_request) Successful in 1m30s
Tests / Build and Test (3.13) (pull_request) Successful in 1m40s
Analysis / SonarCloud (push) Successful in 1m1s
Analysis / SonarCloud (pull_request) Successful in 58s
2025-06-17 18:51:05 +03:00
d70fd4f491 Update dependency mypy to v1.16.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 58s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m16s
Tests / Build and Test (3.13) (pull_request) Successful in 1m10s
Analysis / SonarCloud (push) Successful in 1m9s
2025-06-16 20:06:16 +03:00
c4fb1dd5dd Update dependency pytest-cov to v6.2.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 53s
Tests / Build and Test (3.11) (pull_request) Successful in 1m20s
Tests / Build and Test (3.12) (pull_request) Successful in 1m19s
Tests / Build and Test (3.13) (pull_request) Successful in 1m12s
Analysis / SonarCloud (push) Successful in 1m1s
2025-06-12 13:57:08 +03:00
38bf43a5e7 Update dependency pytest-cov to v6.2.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 50s
Tests / Build and Test (3.11) (pull_request) Successful in 1m24s
Tests / Build and Test (3.12) (pull_request) Successful in 1m34s
Tests / Build and Test (3.13) (pull_request) Successful in 1m11s
Analysis / SonarCloud (push) Successful in 49s
2025-06-12 01:32:32 +03:00
5dff5fa71d Update dependency types-aiofiles to v24.1.0.20250606
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 49s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m10s
Tests / Build and Test (3.13) (pull_request) Successful in 1m8s
Analysis / SonarCloud (push) Successful in 50s
2025-06-06 06:11:29 +03:00
e596658c68 Update dependency pytest to v8.4.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 58s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m8s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
Analysis / SonarCloud (push) Successful in 51s
2025-06-02 20:43:25 +03:00
1e1b04a8ff Update dependency typing-extensions to ~=4.14.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 58s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
Analysis / SonarCloud (push) Successful in 1m7s
2025-06-02 18:39:15 +03:00
69b034c007 Update dependency mypy to v1.16.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 46s
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.12) (pull_request) Successful in 1m10s
Tests / Build and Test (3.13) (pull_request) Successful in 1m7s
Analysis / SonarCloud (push) Successful in 51s
2025-05-29 16:49:31 +03:00
kku
54de950899 Fixed compatibility issue with PEP 639
All checks were successful
Analysis / SonarCloud (push) Successful in 51s
2025-05-28 10:05:30 +02:00
c17a206c44 Update dependency redis to ~=6.2.0
Some checks failed
Analysis / SonarCloud (pull_request) Successful in 48s
Tests / Build and Test (3.11) (pull_request) Failing after 55s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.13) (pull_request) Successful in 1m8s
Analysis / SonarCloud (push) Successful in 50s
2025-05-28 08:54:30 +03:00
dc05eb0ccb Update dependency pytest-asyncio to v1
Some checks failed
Analysis / SonarCloud (pull_request) Successful in 50s
Tests / Build and Test (3.11) (pull_request) Failing after 52s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m20s
Analysis / SonarCloud (push) Successful in 50s
2025-05-26 08:33:47 +03:00
3a7f748d96 Updated the license specification to comply with PEP 639
All checks were successful
Analysis / SonarCloud (push) Successful in 47s
2025-05-18 17:44:18 +02:00
95abf4265c Added support for cache prefix
All checks were successful
Analysis / SonarCloud (push) Successful in 54s
Analysis / SonarCloud (pull_request) Successful in 46s
Tests / Build and Test (3.11) (pull_request) Successful in 1m12s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
2025-05-18 17:29:27 +02:00
84e1cf7ce9 Update dependency types-aiofiles to v24.1.0.20250516
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 41s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.13) (pull_request) Successful in 1m7s
Analysis / SonarCloud (push) Successful in 48s
2025-05-16 06:49:57 +03:00
accd22dd4d Update dependency tox to v4.26.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 43s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.13) (pull_request) Successful in 1m4s
Analysis / SonarCloud (push) Successful in 46s
2025-05-13 18:09:36 +03:00
e296aaa6b3 Update dependency redis to ~=6.1.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m26s
Tests / Build and Test (3.13) (pull_request) Successful in 1m18s
Analysis / SonarCloud (push) Successful in 49s
2025-05-13 16:06:05 +03:00
6de12244ec Update dependency pylint to v3.3.7
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 45s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m8s
Tests / Build and Test (3.13) (pull_request) Successful in 1m9s
Analysis / SonarCloud (push) Successful in 53s
2025-05-04 20:55:27 +03:00
e45266a977 Update dependency redis to v6
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 44s
Tests / Build and Test (3.11) (pull_request) Successful in 1m7s
Tests / Build and Test (3.12) (pull_request) Successful in 1m6s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
Analysis / SonarCloud (push) Successful in 48s
2025-04-30 22:28:51 +03:00
9f1179f330 Update dependency redis to ~=5.3.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 1m36s
Tests / Build and Test (3.11) (pull_request) Successful in 1m23s
Tests / Build and Test (3.12) (pull_request) Successful in 1m23s
Tests / Build and Test (3.13) (pull_request) Successful in 1m22s
Analysis / SonarCloud (push) Successful in 53s
2025-04-30 18:22:21 +03:00
0690a0fe22 Update dependency pytest-cov to v6.1.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m10s
Tests / Build and Test (3.12) (pull_request) Successful in 1m8s
Tests / Build and Test (3.13) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 51s
2025-04-05 17:22:39 +03:00
74f5d638e3 Update dependency pytest-cov to v6.1.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m6s
Tests / Build and Test (3.13) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 1m38s
2025-04-01 14:38:36 +03:00
809e103aa3 Merge pull request 'Update dependency tox to v4.25.0' (#198) from renovate/tox-4.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
Reviewed-on: #198
2025-03-27 17:57:47 +02:00
6171dac7b8 Update dependency tox to v4.25.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.13) (pull_request) Successful in 1m4s
2025-03-27 17:30:45 +02:00
b7f847752c Update dependency types-ujson to v5.10.0.20250326
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 42s
Tests / Build and Test (3.11) (pull_request) Successful in 1m12s
Tests / Build and Test (3.12) (pull_request) Successful in 1m5s
Tests / Build and Test (3.13) (pull_request) Successful in 1m3s
Analysis / SonarCloud (push) Successful in 49s
2025-03-26 07:32:40 +02:00
cb5e6c7bdb Update dependency typing-extensions to ~=4.13.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m8s
Tests / Build and Test (3.13) (pull_request) Successful in 1m4s
Analysis / SonarCloud (push) Successful in 49s
2025-03-26 06:30:43 +02:00
762c20a213 Update dependency types-aiofiles to v24.1.0.20250326
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 50s
Tests / Build and Test (3.11) (pull_request) Successful in 1m31s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.13) (pull_request) Successful in 1m10s
Analysis / SonarCloud (push) Successful in 46s
2025-03-26 05:28:21 +02:00
e34cb7f4b1 Update dependency pytest-asyncio to v0.26.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 47s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m10s
Tests / Build and Test (3.13) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 52s
2025-03-25 08:50:28 +02:00
7908d0b906 Update dependency pylint to v3.3.6
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 41s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m5s
Tests / Build and Test (3.13) (pull_request) Successful in 1m4s
Analysis / SonarCloud (push) Successful in 43s
2025-03-20 13:40:52 +02:00
cc41f4aa83 Update dependency pylint to v3.3.5
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.11) (pull_request) Successful in 1m8s
Tests / Build and Test (3.12) (pull_request) Successful in 1m6s
Tests / Build and Test (3.13) (pull_request) Successful in 1m3s
Analysis / SonarCloud (push) Successful in 45s
2025-03-09 09:34:58 +02:00
f29a6e4896 Update dependency tox to v4.24.2
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 43s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m6s
Tests / Build and Test (3.13) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 51s
2025-03-07 20:38:14 +02:00
2bb62373d6 Merge pull request 'Update dependency pytest to v8.3.5' (#190) from renovate/pytest-8.x into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
Reviewed-on: #190
2025-03-02 16:19:26 +02:00
0ee1c75031 Update dependency pytest to v8.3.5
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 42s
Tests / Build and Test (3.11) (pull_request) Successful in 1m7s
Tests / Build and Test (3.12) (pull_request) Successful in 1m5s
Tests / Build and Test (3.13) (pull_request) Successful in 1m2s
2025-03-02 15:25:02 +02:00
12f7cb6365 Added some basic tests for Cache
All checks were successful
Analysis / SonarCloud (push) Successful in 42s
Analysis / SonarCloud (pull_request) Successful in 49s
Tests / Build and Test (3.11) (pull_request) Successful in 1m18s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
2025-02-16 17:16:05 +01:00
76ee24cd9e Added cache support
All checks were successful
Analysis / SonarCloud (push) Successful in 50s
2025-02-16 17:03:46 +01:00
6d56d9d0f9 Hopefully fixed a circular import
All checks were successful
Analysis / SonarCloud (push) Successful in 47s
2025-02-16 16:58:56 +01:00
554b522400 Added experimental cache support
All checks were successful
Analysis / SonarCloud (push) Successful in 51s
2025-02-16 16:45:22 +01:00
e9abed27f8 Removed caching action
All checks were successful
Analysis / SonarCloud (push) Successful in 52s
2025-02-09 18:55:26 +01:00
845a69491d Silly attempt to fix token issues
Some checks failed
Analysis / SonarCloud (push) Failing after 4s
2025-02-09 18:51:48 +01:00
df2b5efd88 Merge pull request 'Closes #187 and improves documentation' (#188) from bugfix/187 into dev
Some checks failed
Analysis / SonarCloud (push) Failing after 4s
Reviewed-on: #188
2025-02-09 19:41:30 +02:00
6b2be48052 Closes #187 and improves documentation
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 5s
Tests / Build and Test (3.11) (pull_request) Successful in 1m16s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.13) (pull_request) Successful in 1m7s
2025-02-09 18:40:30 +01:00
ad70648ea2 Update dependency mypy to v1.15.0
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m6s
Tests / Build and Test (3.12) (pull_request) Successful in 1m4s
Tests / Build and Test (3.13) (pull_request) Successful in 1m0s
Analysis / SonarCloud (push) Failing after 4s
2025-02-05 06:31:00 +02:00
09b4d512a6 Merge pull request 'Update dependency pytest-asyncio to v0.25.3' (#184) from renovate/pytest-asyncio-0.x into dev
Some checks failed
Analysis / SonarCloud (push) Failing after 3s
Reviewed-on: #184
2025-01-29 10:48:23 +02:00
1473d34ca1 Merge pull request 'Update dependency black to v25' (#185) from renovate/black-25.x into dev
Some checks failed
Analysis / SonarCloud (push) Failing after 4s
Reviewed-on: #185
2025-01-29 10:43:45 +02:00
5fc8ae6a6e Update dependency black to v25
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m9s
Tests / Build and Test (3.12) (pull_request) Successful in 1m5s
Tests / Build and Test (3.13) (pull_request) Successful in 1m4s
2025-01-29 06:29:48 +02:00
8562d7e84c Update dependency pytest-asyncio to v0.25.3
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m20s
Tests / Build and Test (3.13) (pull_request) Successful in 1m18s
2025-01-28 21:04:58 +02:00
258b46d829 Update dependency pylint to v3.3.4
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m12s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
Analysis / SonarCloud (push) Failing after 4s
2025-01-28 15:50:37 +02:00
efec002667 Merge pull request 'Update dependency twine to v6.1.0' (#181) from renovate/twine-6.x into dev
Some checks failed
Analysis / SonarCloud (push) Failing after 3s
Reviewed-on: #181
2025-01-24 16:38:45 +02:00
475eaf9ff3 Update dependency twine to v6.1.0
Some checks failed
Analysis / SonarCloud (pull_request) Failing after 3s
Tests / Build and Test (3.11) (pull_request) Successful in 1m31s
Tests / Build and Test (3.12) (pull_request) Successful in 1m23s
Tests / Build and Test (3.13) (pull_request) Successful in 1m14s
2025-01-21 21:37:09 +02:00
0fcd9f2041 Merge pull request 'Update dependency tox to v4.24.0' (#179) from renovate/tox-4.x into dev
Some checks failed
Analysis / SonarCloud (push) Failing after 4s
Reviewed-on: #179
2025-01-21 21:12:20 +02:00
44d07dc56a Update dependency tox to v4.24.0
Some checks failed
Tests / Build and Test (3.11) (pull_request) Successful in 1m17s
Tests / Build and Test (3.13) (pull_request) Successful in 1m6s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Analysis / SonarCloud (pull_request) Failing after 4s
2025-01-21 20:32:47 +02:00
c5e83c17d3 Disabled pip cache for publish because dependencies are inline
All checks were successful
Analysis / SonarCloud (push) Successful in 42s
2025-01-10 11:23:35 +02:00
129cbd923b Updated cache path for tests
All checks were successful
Analysis / SonarCloud (push) Successful in 41s
2025-01-10 11:22:33 +02:00
1ca126829b Hopefully fixed caching
All checks were successful
Analysis / SonarCloud (push) Successful in 49s
2025-01-10 00:09:05 +01:00
974aebfd1a Bruh, works exactly as bad. I give up... Let's cache this shit.
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 23:08:09 +01:00
ed7fa50dbd SonarQube works like shit, switching back to the old SonarCloud action
All checks were successful
Analysis / SonarCloud (push) Successful in 9m49s
2025-01-09 22:54:21 +01:00
82542de0bb SonarQube doesn't seem to work, one more try with latest
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 22:40:32 +01:00
9021eac87b SonarCloud action is deprecated, replacing with sonarqube one and returning to ubuntu-24.04
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 22:35:53 +01:00
651022ab6e SonarCloud doesn't like 22.04 either, trying ubuntu-latest instead
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 22:33:38 +01:00
f8c6b782a1 SonarCloud doesn't like 24.04, trying 22.04 instead
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 22:32:24 +01:00
a1d0b98858 Replaced ubuntu-latest with ubuntu-24.04
Some checks failed
Analysis / SonarCloud (push) Has been cancelled
2025-01-09 22:18:57 +01:00
fec40b1c44 Update dependency pytest-asyncio to v0.25.2
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 34s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m21s
Tests / Build and Test (3.13) (pull_request) Successful in 1m20s
Analysis / SonarCloud (push) Successful in 46s
2025-01-08 08:41:33 +02:00
kku
e9b9fc6ca1 TEST: Publishing Action
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
Analysis / SonarCloud (pull_request) Successful in 41s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m24s
Tests / Build and Test (3.13) (pull_request) Successful in 1m24s
2025-01-02 13:58:23 +01:00
1da367ccb1 Update dependency pytest-asyncio to v0.25.1
All checks were successful
Analysis / SonarCloud (push) Successful in 41s
Analysis / SonarCloud (pull_request) Successful in 40s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m22s
Tests / Build and Test (3.13) (pull_request) Successful in 1m22s
2025-01-02 08:03:07 +02:00
kku
d5e390fe66 Optimized json_load usage, imports and typing
All checks were successful
Analysis / SonarCloud (push) Successful in 48s
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m22s
Tests / Build and Test (3.12) (pull_request) Successful in 1m27s
Tests / Build and Test (3.13) (pull_request) Successful in 1m29s
2025-01-01 22:34:38 +01:00
kku
ae54bd5cce Bump version to 4.0.2
All checks were successful
Analysis / SonarCloud (push) Successful in 42s
Analysis / SonarCloud (pull_request) Successful in 39s
Tests / Build and Test (3.11) (pull_request) Successful in 1m19s
Tests / Build and Test (3.12) (pull_request) Successful in 1m22s
Tests / Build and Test (3.13) (pull_request) Successful in 1m23s
2024-12-31 11:16:16 +01:00
kku
9ce251d733 Added a quick README for examples (belongs to #60)
All checks were successful
Analysis / SonarCloud (push) Successful in 41s
2024-12-31 11:10:06 +01:00
kku
5dd873d683 Closes #61
All checks were successful
Analysis / SonarCloud (push) Successful in 1m1s
2024-12-31 11:07:24 +01:00
b47bcbe513 Update dependency mypy to v1.14.1
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 35s
Tests / Build and Test (3.11) (pull_request) Successful in 1m14s
Tests / Build and Test (3.12) (pull_request) Successful in 1m37s
Tests / Build and Test (3.13) (pull_request) Successful in 1m22s
Analysis / SonarCloud (push) Successful in 47s
2024-12-30 19:17:40 +02:00
kku
bbbec75f91 Fixed naming conventions
All checks were successful
Analysis / SonarCloud (push) Successful in 44s
2024-12-29 19:27:42 +01:00
kku
94553b602e Fixed imports in examples 2024-12-29 16:27:58 +01:00
kku
3cdd6da506 Added typing_extensions to the dependencies
All checks were successful
Analysis / SonarCloud (push) Successful in 58s
Analysis / SonarCloud (pull_request) Successful in 37s
Tests / Build and Test (3.11) (pull_request) Successful in 1m21s
Tests / Build and Test (3.12) (pull_request) Successful in 1m27s
Tests / Build and Test (3.13) (pull_request) Successful in 1m49s
2024-12-29 16:06:45 +01:00
kku
d24e94b57e Tests are now for 3.11+
All checks were successful
Analysis / SonarCloud (push) Successful in 44s
2024-12-27 18:33:51 +01:00
95584c0e63 Slight documentation improvements
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
2024-12-27 00:37:54 +01:00
a13ef83e82 Merge pull request '4.0.0' (#168) from overhaul-v4 into dev
Some checks failed
Analysis / SonarCloud (push) Successful in 40s
Analysis / SonarCloud (pull_request) Successful in 52s
Tests / Build and Test (3.10) (pull_request) Failing after 58s
Tests / Build and Test (3.11) (pull_request) Successful in 1m5s
Tests / Build and Test (3.12) (pull_request) Successful in 1m16s
Tests / Build and Test (3.9) (pull_request) Failing after 58s
Reviewed-on: #168
2024-12-26 19:44:56 +02:00
0ce4ddcf7c Fixed two broken references
Some checks failed
Analysis / SonarCloud (pull_request) Successful in 42s
Tests / Build and Test (3.10) (pull_request) Failing after 57s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.9) (pull_request) Has been cancelled
2024-12-26 18:37:55 +01:00
aa2c778a6a Removed legacy usage of Union[] 2024-12-26 18:36:57 +01:00
a47a508ecf Update dependency pylint to v3.3.3
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 34s
Tests / Build and Test (3.10) (pull_request) Successful in 1m2s
Tests / Build and Test (3.11) (pull_request) Successful in 1m1s
Tests / Build and Test (3.12) (pull_request) Successful in 1m14s
Tests / Build and Test (3.9) (pull_request) Successful in 1m3s
Analysis / SonarCloud (push) Successful in 39s
2024-12-24 05:27:30 +02:00
bdd649bdbe Update dependency types-aiofiles to v24.1.0.20241221
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.9) (pull_request) Successful in 1m6s
Analysis / SonarCloud (push) Successful in 44s
2024-12-21 05:18:48 +02:00
e6d9beec81 Update dependency mypy to v1.14.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 35s
Tests / Build and Test (3.10) (pull_request) Successful in 3m26s
Tests / Build and Test (3.11) (pull_request) Successful in 1m15s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.9) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 46s
2024-12-20 17:47:59 +02:00
kku
95d04308bd WIP: Overhaul for 4.0.0 2024-12-18 14:16:37 +01:00
kku
5e479ddc79 Closes #161
All checks were successful
Analysis / SonarCloud (push) Successful in 48s
2024-12-18 13:53:09 +01:00
40827e70a1 Bump version to fix metadata
All checks were successful
Analysis / SonarCloud (push) Successful in 42s
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m13s
Tests / Build and Test (3.9) (pull_request) Successful in 1m8s
2024-12-16 22:54:41 +01:00
7e03a0c779 Update dependency pytest-asyncio to v0.25.0
All checks were successful
Analysis / SonarCloud (push) Successful in 48s
Analysis / SonarCloud (pull_request) Successful in 46s
Tests / Build and Test (3.10) (pull_request) Successful in 1m6s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m10s
Tests / Build and Test (3.9) (pull_request) Successful in 1m6s
2024-12-13 08:58:25 +02:00
5f4d0b09f9 Update dependency pylint to v3.3.2
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 35s
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m6s
Tests / Build and Test (3.12) (pull_request) Successful in 1m13s
Tests / Build and Test (3.9) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 40s
2024-12-01 21:32:47 +02:00
f6596d1db6 Update dependency pytest to v8.3.4
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 38s
Tests / Build and Test (3.10) (pull_request) Successful in 1m2s
Tests / Build and Test (3.11) (pull_request) Successful in 1m1s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.9) (pull_request) Successful in 1m5s
Analysis / SonarCloud (push) Successful in 41s
2024-12-01 15:17:19 +02:00
cbc4fc36a1 Update dependency apscheduler to ~=3.11.0
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 57s
Tests / Build and Test (3.10) (pull_request) Successful in 1m11s
Tests / Build and Test (3.11) (pull_request) Successful in 1m11s
Tests / Build and Test (3.12) (pull_request) Successful in 1m16s
Tests / Build and Test (3.9) (pull_request) Successful in 1m10s
Analysis / SonarCloud (push) Successful in 47s
2024-11-24 21:56:08 +02:00
d20d07bb6a Fixed a few things mentioned in SonarCloud
All checks were successful
Analysis / SonarCloud (push) Successful in 40s
2024-10-29 23:19:52 +01:00
4ee704b41e Merge pull request 'Removed explicit action host' (#154) from profitroll/actions-fix into dev
All checks were successful
Analysis / SonarCloud (push) Successful in 40s
Reviewed-on: #154
2024-10-29 23:57:43 +02:00
02cfc42f60 Separated SonarCloud from normal tests
All checks were successful
Analysis / SonarCloud (pull_request) Successful in 40s
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m8s
Tests / Build and Test (3.9) (pull_request) Successful in 1m8s
2024-10-29 22:55:01 +01:00
2757bd6b72 Removed explicit action host
All checks were successful
Tests / Build and Test (3.10) (pull_request) Successful in 1m4s
Tests / Build and Test (3.11) (pull_request) Successful in 1m1s
Tests / Build and Test (3.12) (pull_request) Successful in 1m7s
Tests / Build and Test (3.9) (pull_request) Successful in 1m4s
Tests / SonarCloud (pull_request) Successful in 37s
2024-10-29 23:42:07 +02:00
fe71860faa Merge pull request 'Test PR' (#152) from profitroll/python-3.12 into dev
Reviewed-on: #152
2024-10-29 23:40:19 +02:00
8fc5a18fab Merge pull request 'Update dependency pytest-cov to v6' (#153) from renovate/pytest-cov-6.x into dev
Reviewed-on: #153
2024-10-29 23:40:10 +02:00
54d98df7b7 Fixed Gitea Act container 2024-10-29 23:39:57 +02:00
c9e4b3b916 Update dependency pytest-cov to v6
Some checks failed
Tests / Build and Test (3.10) (pull_request) Successful in 1m6s
Tests / Build and Test (3.11) (pull_request) Successful in 1m4s
Tests / Build and Test (3.12) (pull_request) Successful in 1m15s
Tests / Build and Test (3.9) (pull_request) Successful in 1m4s
Tests / SonarCloud (pull_request) Failing after 0s
2024-10-29 22:25:05 +02:00
acb77049d1 Changed the sonarcloud action link
Some checks failed
Tests / Build and Test (3.10) (pull_request) Successful in 1m7s
Tests / Build and Test (3.11) (pull_request) Successful in 1m2s
Tests / Build and Test (3.12) (pull_request) Successful in 1m9s
Tests / Build and Test (3.9) (pull_request) Successful in 1m3s
Tests / SonarCloud (pull_request) Failing after 0s
2024-10-29 17:55:02 +02:00
d41eb742e5 Add sonar-project.properties
Some checks failed
Tests / Build and Test (3.10) (pull_request) Successful in 1m13s
Tests / Build and Test (3.11) (pull_request) Successful in 1m5s
Tests / Build and Test (3.12) (pull_request) Successful in 1m11s
Tests / Build and Test (3.9) (pull_request) Successful in 1m8s
Tests / SonarCloud (pull_request) Failing after 0s
2024-10-29 17:38:41 +02:00
377ba928ba Added sonarcloud job 2024-10-29 17:37:16 +02:00
b9ae1c2149 Merge pull request 'Update dependency mypy to v1.13.0' (#151) from renovate/mypy-1.x into dev
Reviewed-on: #151
2024-10-23 02:12:25 +03:00
c670521bb2 Update dependency mypy to v1.13.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m8s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-10-23 01:28:30 +03:00
c175b4634c Update dependency tox to v4.23.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m10s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-10-22 18:11:45 +03:00
54feb5ff5b Update dependency tox to v4.23.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.12) (pull_request) Successful in 1m9s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-10-22 08:50:37 +03:00
1d32c5e1c3 Update dependency mypy to v1.12.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m9s
Tests / test (3.9) (pull_request) Successful in 1m5s
2024-10-20 04:00:40 +03:00
eeaa71606b Update dependency tox to v4.23.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m9s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-10-17 07:39:43 +03:00
e130633017 Update dependency tox to v4.22.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m2s
Tests / test (3.12) (pull_request) Successful in 1m10s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-10-15 22:29:37 +03:00
c71b07695f Update dependency mypy to v1.12.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m5s
Tests / test (3.11) (pull_request) Successful in 1m2s
Tests / test (3.12) (pull_request) Successful in 1m8s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-10-14 15:21:36 +03:00
03ad8e2144 Update dependency black to v24.10.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m6s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m10s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-10-07 22:45:07 +03:00
c7d0d2793b Update dependency build to v1.2.2.post1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.12) (pull_request) Successful in 1m8s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-10-06 21:19:35 +03:00
41c8a6989b Update dependency tox to v4.21.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m2s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.12) (pull_request) Successful in 1m9s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-10-03 19:11:48 +03:00
b8f5d59a4f Update dependency tox to v4.21.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m2s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.12) (pull_request) Successful in 1m8s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-10-03 00:23:12 +03:00
67befe6bdb Update dependency tox to v4.21.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m1s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.12) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m1s
2024-10-01 07:39:37 +03:00
63cecf7ab8 Update dependency pylint to v3.3.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m4s
Tests / test (3.12) (pull_request) Successful in 1m19s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-09-24 15:11:18 +03:00
6652e8aff7 Merge pull request 'Update dependency pylint to v3.3.0' (#138) from renovate/pylint-3.x into dev
Reviewed-on: #138
2024-09-21 01:13:18 +03:00
a341ffd41d Dropped Python 3.8 support and added 3.12 support instead 2024-09-21 00:11:13 +02:00
60cc3f22d8 Update dependency pylint to v3.3.0
Some checks failed
Tests / test (3.10) (pull_request) Successful in 1m10s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Failing after 14s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-09-20 16:22:35 +03:00
78a5fb886f Update dependency tox to v4.20.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m2s
Tests / test (3.11) (pull_request) Successful in 59s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-09-19 06:56:21 +03:00
f8ebaff82d Update dependency tox to v4.19.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-09-17 22:31:53 +03:00
8c15d20dc6 Update dependency pytest to v8.3.3
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.8) (pull_request) Successful in 1m7s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-09-10 14:28:30 +03:00
8587030c23 Update dependency tox to v4.18.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m1s
Tests / test (3.11) (pull_request) Successful in 59s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-09-07 19:48:32 +03:00
b00d4c0281 Update dependency build to v1.2.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m2s
Tests / test (3.11) (pull_request) Successful in 59s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-09-07 00:00:20 +03:00
dcf5a24d2a Update dependency pylint to v3.2.7
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m1s
Tests / test (3.11) (pull_request) Successful in 59s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-08-31 18:05:42 +03:00
ca989b1e82 Update dependency mypy to v1.11.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m1s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.8) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-08-25 02:15:07 +03:00
36698b105c Update dependency pytest-asyncio to v0.24.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m7s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.8) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-08-22 11:53:29 +03:00
b366bb5c6f Update dependency tox to v4.18.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-08-13 20:32:44 +03:00
7a67c48ad6 Update dependency tox to v4.17.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m5s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Successful in 1m3s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-08-07 21:22:32 +03:00
8b18449e23 Update dependency tox to v4.17.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m5s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-08-05 23:46:38 +03:00
fcb08adc59 Update dependency black to v24.8.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.8) (pull_request) Successful in 1m5s
Tests / test (3.9) (pull_request) Successful in 1m10s
2024-08-02 21:37:29 +03:00
300df5b828 Update dependency mypy to v1.11.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m6s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-07-31 01:54:06 +03:00
3d9489eb0e Fixed incorrect test declaration 2024-07-26 19:58:26 +02:00
b00e188859 Update dependency pytest to v8.3.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m17s
Tests / test (3.8) (pull_request) Successful in 1m34s
Tests / test (3.9) (pull_request) Successful in 1m5s
2024-07-25 13:43:06 +03:00
a5edaa035e Update dependency pylint to v3.2.6
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m24s
Tests / test (3.11) (pull_request) Successful in 1m2s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-07-21 23:08:20 +03:00
891d8e416b Update dependency pytest to v8.3.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-07-20 19:39:38 +03:00
1e3e6cc7d0 Update dependency pytest to v8.3.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m1s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Successful in 1m23s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-07-20 18:35:48 +03:00
4c003de0d3 Merge pull request 'Update dependency mypy to v1.11.0' (#120) from renovate/mypy-1.x into dev
Reviewed-on: #120
2024-07-19 21:54:20 +03:00
80fd18d101 Update dependency mypy to v1.11.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Successful in 2m37s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-07-19 21:29:57 +03:00
35a85c8cab Update dependency pytest-asyncio to v0.23.8
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m23s
Tests / test (3.11) (pull_request) Successful in 1m0s
Tests / test (3.8) (pull_request) Successful in 1m3s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-07-17 21:02:27 +03:00
8f89d93fdc Bump version to 3.2.3
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m1s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-07-09 22:44:35 +02:00
8dc389d1b3 Bump pyrofork to ~=2.3.32 2024-07-09 22:43:19 +02:00
70b5994ecb Update dependency py-cord to ~=2.6.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m0s
Tests / test (3.11) (pull_request) Successful in 59s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-07-09 21:34:58 +03:00
ea45ccbad6 Update dependency tox to v4.16.0
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m0s
Tests / test (3.11) (pull_request) Successful in 57s
Tests / test (3.8) (pull_request) Successful in 1m1s
Tests / test (3.9) (pull_request) Successful in 1m0s
2024-07-03 07:05:32 +03:00
7d287ec46c Update dependency pylint to v3.2.5
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m0s
Tests / test (3.11) (pull_request) Successful in 58s
Tests / test (3.8) (pull_request) Successful in 1m1s
Tests / test (3.9) (pull_request) Successful in 1m2s
2024-06-28 17:03:25 +03:00
d317443960 Merge pull request 'Update dependency pylint to v3.2.4' (#114) from renovate/pylint-3.x into dev
Reviewed-on: #114
2024-06-26 11:54:38 +03:00
4daf6a5a5e Update dependency pylint to v3.2.4
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m0s
Tests / test (3.11) (pull_request) Successful in 58s
Tests / test (3.8) (pull_request) Successful in 1m2s
Tests / test (3.9) (pull_request) Successful in 1m0s
2024-06-26 11:26:23 +03:00
b81700da77 Update dependency types-aiofiles to v24
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m0s
Tests / test (3.11) (pull_request) Successful in 58s
Tests / test (3.8) (pull_request) Successful in 1m1s
Tests / test (3.9) (pull_request) Successful in 1m1s
2024-06-26 06:07:50 +03:00
910efda16c Update dependency mypy to v1.10.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 59s
Tests / test (3.11) (pull_request) Successful in 56s
Tests / test (3.8) (pull_request) Successful in 1m1s
Tests / test (3.9) (pull_request) Successful in 59s
2024-06-25 03:40:01 +03:00
c91591468b Update dependency types-aiofiles to v23.2.0.20240623
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m5s
Tests / test (3.11) (pull_request) Successful in 1m3s
Tests / test (3.8) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m4s
2024-06-23 05:50:47 +03:00
dad6717706 Update dependency pylint to v3.2.3
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m3s
Tests / test (3.11) (pull_request) Successful in 1m10s
Tests / test (3.8) (pull_request) Successful in 1m4s
Tests / test (3.9) (pull_request) Successful in 1m5s
2024-06-06 18:02:20 +03:00
263522690f Update dependency tox to v4.15.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m18s
Tests / test (3.11) (pull_request) Successful in 1m2s
Tests / test (3.8) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m7s
2024-06-06 04:18:39 +03:00
1bd4b6afe9 Update dependency pytest to v8.2.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m5s
Tests / test (3.11) (pull_request) Successful in 1m4s
Tests / test (3.8) (pull_request) Successful in 1m6s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-06-04 17:26:07 +03:00
bd3fbd7c2c Bump version to 3.2.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m6s
Tests / test (3.11) (pull_request) Successful in 1m30s
Tests / test (3.8) (pull_request) Successful in 1m5s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-05-26 21:40:28 +02:00
64ba9efa34 Replaced hasattr in dumps with supports_argument 2024-05-26 21:39:55 +02:00
15f9274050 Bump version to 3.2.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m4s
Tests / test (3.11) (pull_request) Successful in 1m18s
Tests / test (3.8) (pull_request) Successful in 1m30s
Tests / test (3.9) (pull_request) Successful in 1m26s
2024-05-26 16:50:42 +02:00
6d3c20479d Added optional missing_ok to config_delete 2024-05-26 16:50:14 +02:00
dfaadfd769 upload-artifact@v3 instead of upload-artifact@v4
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m28s
Tests / test (3.11) (pull_request) Successful in 1m2s
Tests / test (3.8) (pull_request) Successful in 1m5s
Tests / test (3.9) (pull_request) Successful in 1m6s
2024-05-26 16:21:53 +02:00
66ede3d60f Added build task
Some checks failed
Tests / test (3.10) (pull_request) Failing after 1m5s
Tests / test (3.11) (pull_request) Failing after 1m3s
Tests / test (3.8) (pull_request) Failing after 1m5s
Tests / test (3.9) (pull_request) Failing after 1m29s
2024-05-26 15:48:19 +02:00
3c3de1436e Added artifacts upload
All checks were successful
Tests / test (3.10) (pull_request) Successful in 1m32s
Tests / test (3.11) (pull_request) Successful in 54s
Tests / test (3.8) (pull_request) Successful in 1m24s
Tests / test (3.9) (pull_request) Successful in 58s
2024-05-26 15:43:53 +02:00
cfeadfdd32 Bumped version to 3.2.0 2024-05-26 15:40:54 +02:00
7032bef956 Added nested_delete and config_delete 2024-05-26 15:40:29 +02:00
ce691b2eda Update dependency pylint to v3.2.2
All checks were successful
Tests / test (3.10) (pull_request) Successful in 57s
Tests / test (3.11) (pull_request) Successful in 57s
Tests / test (3.8) (pull_request) Successful in 58s
Tests / test (3.9) (pull_request) Successful in 1m3s
2024-05-20 11:10:24 +03:00
a383959c9f Update dependency pytest to v8.2.1
All checks were successful
Tests / test (3.10) (pull_request) Successful in 55s
Tests / test (3.11) (pull_request) Successful in 53s
Tests / test (3.8) (pull_request) Successful in 1m24s
Tests / test (3.9) (pull_request) Successful in 1m24s
2024-05-19 22:27:08 +03:00
61 changed files with 1583 additions and 784 deletions

View File

@@ -0,0 +1,23 @@
name: Analysis
on:
push:
branches:
- main
- dev
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
sonarcloud:
name: SonarCloud
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4.2.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

View File

@@ -0,0 +1,67 @@
name: Upload Python Package
on:
release:
types: [ published ]
permissions:
contents: read
jobs:
release-build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Build release distributions
run: |
python -m pip install build
python -m build
- name: Upload distributions
uses: christopherhx/gitea-upload-artifact@v4
with:
name: release-dists
path: dist/
gitea-publish:
runs-on: ubuntu-24.04
needs: release-build
permissions:
id-token: write
environment:
name: gitea
url: https://git.end-play.xyz/profitroll/-/packages/pypi/libbot
env:
GITHUB_WORKFLOW_REF: ${{ gitea.workflow_ref }}
INPUT_REPOSITORY_URL: https://git.end-play.xyz/api/packages/profitroll/pypi
steps:
- name: Retrieve release distributions
uses: christopherhx/gitea-download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_GITEA_API_TOKEN }}
repository-url: https://git.end-play.xyz/api/packages/profitroll/pypi
pypi-publish:
runs-on: ubuntu-24.04
needs: release-build
permissions:
id-token: write
environment:
name: pypi
env:
GITHUB_WORKFLOW_REF: ${{ gitea.workflow_ref }}
steps:
- name: Retrieve release distributions
uses: christopherhx/gitea-download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PYPI_API_TOKEN }}

View File

@@ -10,23 +10,30 @@ on:
jobs:
test:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
name: Build and Test
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: [ "3.11", "3.12", "3.13" ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: './requirements/*'
env:
AGENT_TOOLSDIRECTORY: /opt/hostedtoolcache
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
pip install tox tox-gh-actions build
- name: Test with tox
run: tox
- name: Build
run: python -m build
- uses: actions/upload-artifact@v3
with:
name: Artifacts
path: dist/*

2
.gitignore vendored
View File

@@ -166,3 +166,5 @@ venv_linux/
venv_windows/
.vscode/
tests/.tmp/

View File

@@ -16,6 +16,7 @@ There are different sub-packages available:
* pyrogram - Telegram bots with Pyrogram's fork "Pyrofork"
* pycord - Discord bots with Pycord
* speed - Performance improvements
* cache - Support for Redis and Memcached
* dev - Dependencies for package development purposes
You can freely choose any sub-package you want, as well as add multiple (comma-separated) or none at all.
@@ -36,19 +37,20 @@ pip install libbot[pycord,speed]
### Pyrogram
```python
from libbot.pyrogram import PyroClient
import sys
from libbot.pyrogram.classes import PyroClient
def main():
client = PyroClient(scheduler=scheduler)
client: PyroClient = PyroClient()
try:
client.run()
except KeyboardInterrupt:
print("Shutting down...")
finally:
if client.scheduler is not None:
client.scheduler.shutdown()
exit()
sys.exit()
if __name__ == "__main__":
@@ -58,29 +60,33 @@ if __name__ == "__main__":
### Pycord
```python
import asyncio
from asyncio import AbstractEventLoop
from discord import Intents
from libbot import sync
from libbot.pycord import PycordBot
from libbot.utils import config_get
from libbot.pycord.classes import PycordBot
async def main():
intents = Intents.default()
bot = PycordBot(intents=intents)
intents: Intents = Intents.default()
bot: PycordBot = PycordBot(intents=intents)
bot.load_extension("cogs")
try:
await bot.start(sync.config_get("bot_token", "bot"))
await bot.start(config_get("bot_token", "bot"))
except KeyboardInterrupt:
logger.warning("Shutting down...")
print("Shutting down...")
await bot.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop: AbstractEventLoop = asyncio.get_event_loop()
loop.run_until_complete(main())
```
## Config examples
For bot config examples please check the examples directory. Without a valid config file, the bot won't start at all, so you need to make sure the correct config file is used.
For bot config examples please check the examples directory. Without a valid config file, the bot won't start at all, so
you need to make sure the correct config file is used.

4
examples/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Examples
If you're looking for Pyrogram usage examples, please take a look at
the [PyrogramBotBase](https://git.end-play.xyz/profitroll/PyrogramBotBase) repository.

View File

@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=62.6", "wheel"]
requires = ["setuptools>=77.0.3", "wheel"]
build-backend = "setuptools.build_meta"
[project]
@@ -8,17 +8,16 @@ dynamic = ["version", "dependencies", "optional-dependencies"]
authors = [{ name = "Profitroll" }]
description = "Universal bot library with functions needed for basic Discord/Telegram bot development."
readme = "README.md"
requires-python = ">=3.8"
license = { text = "GPLv3" }
requires-python = ">=3.11"
license = "GPL-3.0"
license-files = ["LICENSE"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
]
@@ -32,6 +31,7 @@ dev = { file = "requirements/dev.txt" }
pycord = { file = "requirements/pycord.txt" }
pyrogram = { file = "requirements/pyrogram.txt" }
speed = { file = "requirements/speed.txt" }
cache = { file = "requirements/cache.txt" }
[project.urls]
Source = "https://git.end-play.xyz/profitroll/LibBotUniversal"
@@ -42,7 +42,8 @@ Tracker = "https://git.end-play.xyz/profitroll/LibBotUniversal/issues"
where = ["src"]
[tool.black]
target-version = ['py38', 'py39', 'py310', 'py311']
line-length = 108
target-version = ["py311", "py312", "py313"]
[tool.isort]
profile = "black"
@@ -52,6 +53,8 @@ minversion = "6.0"
python_files = ["test_*.py"]
pythonpath = "."
testpaths = ["tests"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
[tool.mypy]
namespace_packages = true
@@ -59,9 +62,12 @@ install_types = true
strict = true
show_error_codes = true
[tool.pylint]
disable = ["line-too-long"]
[tool.pylint.main]
extension-pkg-whitelist = ["ujson"]
py-version = 3.8
py-version = 3.11
[tool.coverage.run]
source = ["libbot"]

View File

@@ -1 +1,2 @@
aiofiles>=23.0.0
typing-extensions~=4.15.0

2
requirements/cache.txt Normal file
View File

@@ -0,0 +1,2 @@
pymemcache~=4.0.0
redis~=6.4.0

View File

@@ -1,11 +1,12 @@
black==24.4.2
build==1.2.1
black==25.1.0
build==1.3.0
isort==5.13.2
mypy==1.10.0
pylint==3.2.1
pytest-asyncio==0.23.7
pytest-cov==5.0.0
pytest==8.2.0
tox==4.15.0
types-aiofiles==23.2.0.20240403
types-ujson==5.10.0.20240515
mypy==1.17.1
pylint==3.3.8
pytest-asyncio==1.1.0
pytest-cov==6.2.1
pytest==8.4.1
tox==4.29.0
twine==6.1.0
types-aiofiles==24.1.0.20250822
types-ujson==5.10.0.20250822

View File

@@ -1,2 +1,2 @@
apscheduler~=3.10.4
py-cord~=2.5.0
apscheduler~=3.11.0
py-cord~=2.6.0

View File

@@ -1,2 +1,2 @@
apscheduler~=3.10.4
pyrofork~=2.3.21.post3
apscheduler~=3.11.0
pyrofork~=2.3.32

View File

@@ -1 +1 @@
ujson~=5.10.0
ujson~=5.11.0

2
sonar-project.properties Normal file
View File

@@ -0,0 +1,2 @@
sonar.projectKey=profitroll_libbot
sonar.organization=profitroll

View File

@@ -1,6 +1,5 @@
__version__ = "3.1.0"
__version__ = "4.4.0"
__license__ = "GPL3"
__author__ = "Profitroll"
from . import errors, i18n, pycord, pyrogram, sync
from .__main__ import *
from . import utils, errors, i18n, pycord, pyrogram

View File

@@ -1,95 +0,0 @@
from pathlib import Path
from typing import Any, Union
import aiofiles
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
from libbot.sync import nested_set
async def json_read(path: Union[str, Path]) -> Any:
"""Read contents of a JSON file
### Args:
* path (`Union[str, Path]`): Path-like object or path as a string
### Returns:
* `Any`: File contents
"""
async with aiofiles.open(str(path), mode="r", encoding="utf-8") as f:
data = await f.read()
return loads(data)
async def json_write(data: Any, path: Union[str, Path]) -> None:
"""Write contents to a JSON file
### Args:
* data (`Any`): Contents to write. Must be a JSON serializable
* path (`Union[str, Path]`): Path-like object or path as a string of a destination
"""
async with aiofiles.open(str(path), mode="w", encoding="utf-8") as f:
await f.write(
dumps(data, ensure_ascii=False, escape_forward_slashes=False, indent=4)
if hasattr(dumps, "escape_forward_slashes")
else dumps(data, ensure_ascii=False, indent=4)
)
async def config_get(
key: str, *path: str, config_file: Union[str, Path] = "config.json"
) -> Any:
"""Get a value of the config key by its path provided
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`
### Args:
* key (`str`): Key that contains the value
* *path (`str`): Path to the key that contains the value
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Returns:
* `Any`: Key's value
### Example:
Get the "salary" of "Pete" from this JSON structure:
```json
{
"users": {
"Pete": {
"salary": 10.0
}
}
}
```
This can be easily done with the following code:
```python
import libbot
salary = await libbot.config_get("salary", "users", "Pete")
```
"""
this_key = await json_read(config_file)
for dict_key in path:
this_key = this_key[dict_key]
return this_key[key]
async def config_set(
key: str, value: Any, *path: str, config_file: Union[str, Path] = "config.json"
) -> None:
"""Set config's key by its path to the value
### Args:
* key (`str`): Key that leads to the value
* value (`Any`): Any JSON serializable data
* *path (`str`): Path to the key of the target
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
"""
await json_write(
nested_set(await json_read(config_file), value, *(*path, key)), config_file
)
return

2
src/libbot/cache/__init__.py vendored Normal file
View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[pycord] is not installed

3
src/libbot/cache/classes/__init__.py vendored Normal file
View File

@@ -0,0 +1,3 @@
from .cache import Cache
from .cache_memcached import CacheMemcached
from .cache_redis import CacheRedis

44
src/libbot/cache/classes/cache.py vendored Normal file
View File

@@ -0,0 +1,44 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
import pymemcache
import redis
class Cache(ABC):
client: pymemcache.Client | redis.Redis
@classmethod
@abstractmethod
def from_config(cls, engine_config: Dict[str, Any]) -> Any:
pass
@abstractmethod
def get_json(self, key: str) -> Any | None:
# TODO This method must also carry out ObjectId conversion!
pass
@abstractmethod
def get_string(self, key: str) -> str | None:
pass
@abstractmethod
def get_object(self, key: str) -> Any | None:
pass
@abstractmethod
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
# TODO This method must also carry out ObjectId conversion!
pass
@abstractmethod
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
pass
@abstractmethod
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
pass
@abstractmethod
def delete(self, key: str) -> None:
pass

View File

@@ -0,0 +1,112 @@
import logging
from logging import Logger
from typing import Dict, Any, Optional
from pymemcache import Client
from .cache import Cache
from ..utils._objects import _json_to_string, _string_to_json
logger: Logger = logging.getLogger(__name__)
class CacheMemcached(Cache):
client: Client
def __init__(
self, client: Client, prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None
) -> None:
self.client: Client = client
self.prefix: str | None = prefix
self.default_ttl_seconds: int = default_ttl_seconds if default_ttl_seconds is not None else 0
logger.info("Initialized Memcached for caching")
@classmethod
def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None) -> "CacheMemcached":
if "uri" not in engine_config:
raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
)
return cls(Client(engine_config["uri"], default_noreply=True), prefix=prefix, default_ttl_seconds=default_ttl_seconds)
def _get_prefixed_key(self, key: str) -> str:
return key if self.prefix is None else f"{self.prefix}_{key}"
def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try:
result: Any | None = self.client.get(key, None)
logger.debug(
"Got json cache key '%s'%s",
key,
"" if result is not None else " (not found)",
)
except Exception as exc:
logger.error("Could not get json cache key '%s' due to: %s", key, exc)
return None
return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try:
result: str | None = self.client.get(key, None)
logger.debug(
"Got string cache key '%s'%s",
key,
"" if result is not None else " (not found)",
)
return result
except Exception as exc:
logger.error("Could not get string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary deserialization
def get_object(self, key: str) -> Any | None:
raise NotImplementedError()
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
try:
self.client.set(
key,
_json_to_string(value),
expire=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds,
)
logger.debug("Set json cache key '%s'", key)
except Exception as exc:
logger.error("Could not set json cache key '%s' due to: %s", key, exc)
return None
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
try:
self.client.set(
key, value, expire=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds
)
logger.debug("Set string cache key '%s'", key)
except Exception as exc:
logger.error("Could not set string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary serialization
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
raise NotImplementedError()
def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try:
self.client.delete(key)
logger.debug("Deleted cache key '%s'", key)
except Exception as exc:
logger.error("Could not delete cache key '%s' due to: %s", key, exc)

110
src/libbot/cache/classes/cache_redis.py vendored Normal file
View File

@@ -0,0 +1,110 @@
import logging
from logging import Logger
from typing import Dict, Any, Optional
from redis import Redis
from .cache import Cache
from ..utils._objects import _json_to_string, _string_to_json
logger: Logger = logging.getLogger(__name__)
class CacheRedis(Cache):
client: Redis
def __init__(
self, client: Redis, prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None
) -> None:
self.client: Redis = client
self.prefix: str | None = prefix
self.default_ttl_seconds: int | None = default_ttl_seconds
logger.info("Initialized Redis for caching")
@classmethod
def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None) -> Any:
if "uri" not in engine_config:
raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
)
return cls(Redis.from_url(engine_config["uri"]), prefix=prefix, default_ttl_seconds=default_ttl_seconds)
def _get_prefixed_key(self, key: str) -> str:
return key if self.prefix is None else f"{self.prefix}_{key}"
def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try:
result: Any | None = self.client.get(key)
logger.debug(
"Got json cache key '%s'%s",
key,
"" if result is not None else " (not found)",
)
except Exception as exc:
logger.error("Could not get json cache key '%s' due to: %s", key, exc)
return None
return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try:
result: str | None = self.client.get(key)
logger.debug(
"Got string cache key '%s'%s",
key,
"" if result is not None else " (not found)",
)
return result
except Exception as exc:
logger.error("Could not get string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary deserialization
def get_object(self, key: str) -> Any | None:
raise NotImplementedError()
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
try:
self.client.set(
key,
_json_to_string(value),
ex=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds,
)
logger.debug("Set json cache key '%s'", key)
except Exception as exc:
logger.error("Could not set json cache key '%s' due to: %s", key, exc)
return None
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
try:
self.client.set(key, value, ex=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds)
logger.debug("Set string cache key '%s'", key)
except Exception as exc:
logger.error("Could not set string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary serialization
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
raise NotImplementedError()
def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try:
self.client.delete(key)
logger.debug("Deleted cache key '%s'", key)
except Exception as exc:
logger.error("Could not delete cache key '%s' due to: %s", key, exc)

1
src/libbot/cache/manager/__init__.py vendored Normal file
View File

@@ -0,0 +1 @@
from .manager import create_cache_client

37
src/libbot/cache/manager/manager.py vendored Normal file
View File

@@ -0,0 +1,37 @@
from typing import Dict, Any, Literal, Optional
from ..classes import CacheMemcached, CacheRedis
def create_cache_client(
config: Dict[str, Any],
engine: Literal["memcached", "redis"] | None = None,
prefix: Optional[str] = None,
default_ttl_seconds: Optional[int] = None,
) -> CacheMemcached | CacheRedis:
"""Create a cache client of a provided type.
Args:
config (Dict[str, Any]): Cache client configuration.
engine (Literal["memcached", "redis"] | None): Cache engine to use. Defaults to None.
prefix (:obj:`str`, optional): Prefix used for each key-value pair. Defaults to None (no prefix).
default_ttl_seconds (:obj:`int`, optional): Default TTL for values (in seconds). Defaults to None (does not expire).
Returns:
CacheMemcached | CacheRedis: Cache client.
"""
if engine not in ["memcached", "redis"] or engine is None:
raise KeyError(f"Incorrect cache engine provided. Expected 'memcached' or 'redis', got '{engine}'")
if "cache" not in config or engine not in config["cache"]:
raise KeyError(
f"Cache configuration is invalid. Please check if all keys are set (engine: '{engine}')"
)
match engine:
case "memcached":
return CacheMemcached.from_config(config["cache"][engine], prefix=prefix, default_ttl_seconds=default_ttl_seconds)
case "redis":
return CacheRedis.from_config(config["cache"][engine], prefix=prefix, default_ttl_seconds=default_ttl_seconds)
case _:
raise KeyError(f"Cache implementation for the engine '{engine}' is not present.")

0
src/libbot/cache/utils/__init__.py vendored Normal file
View File

42
src/libbot/cache/utils/_objects.py vendored Normal file
View File

@@ -0,0 +1,42 @@
import logging
from copy import deepcopy
from logging import Logger
from typing import Any
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
logger: Logger = logging.getLogger(__name__)
try:
from bson import ObjectId
except ImportError:
logger.warning(
"Could not import bson.ObjectId. PyMongo conversions will not be supported by the cache. It's safe to ignore this message if you do not use MongoDB."
)
def _json_to_string(json_object: Any) -> str:
json_object_copy: Any = deepcopy(json_object)
if isinstance(json_object_copy, dict) and "_id" in json_object_copy:
json_object_copy["_id"] = str(json_object_copy["_id"])
return dumps(json_object_copy, ensure_ascii=False, indent=0, escape_forward_slashes=False)
def _string_to_json(json_string: str) -> Any:
json_object: Any = loads(json_string)
if "_id" in json_object:
try:
json_object["_id"] = ObjectId(json_object["_id"])
except NameError:
logger.debug(
"Tried to convert attribute '_id' with value '%s' but bson.ObjectId is not present, skipping the conversion.",
json_object["_id"],
)
return json_object

View File

@@ -1,15 +1,15 @@
from typing import Any, List, Optional, Union
from typing import Any, List, Optional
class ConfigKeyError(Exception):
"""Raised when config key is not found.
### Attributes:
* key (`Union[str, List[str]]`): Missing config key.
* key (`str | List[str]`): Missing config key.
"""
def __init__(self, key: Union[str, List[str]]) -> None:
self.key: Union[str, List[str]] = key
def __init__(self, key: str | List[str]) -> None:
self.key: str | List[str] = key
super().__init__(
f"Config key {'.'.join(key) if isinstance(key, list) else key} is missing. Please set in your config file."
)
@@ -22,12 +22,12 @@ class ConfigValueError(Exception):
"""Raised when config key's value is invalid.
### Attributes:
* key (`Union[str, List[str]]`): Invalid config key.
* key (`str | List[str]`): Invalid config key.
* value (`Optional[Any]`): Key's correct value.
"""
def __init__(self, key: Union[str, List[str]], value: Optional[Any] = None) -> None:
self.key: Union[str, List[str]] = key
def __init__(self, key: str | List[str], value: Optional[Any] = None) -> None:
self.key: str | List[str] = key
self.value: Optional[Any] = value
super().__init__(
f"Config key {'.'.join(key) if isinstance(key, list) else key} has invalid value. {f'Must be {value}. ' if value else ''}Please set in your config file."

View File

@@ -1,118 +1,2 @@
from os import listdir
from pathlib import Path
from typing import Any, Dict, Union
import libbot
from libbot.i18n import sync
from libbot.i18n.classes.bot_locale import BotLocale
async def _(
key: str,
*args: str,
locale: Union[str, None] = "en",
locales_root: Union[str, Path] = Path("locale"),
) -> Any:
"""Get value of locale string
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locale (`Union[str, None]`): Locale to looked up in. Defaults to `"en"`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list`
"""
locale = libbot.sync.config_get("locale") if locale is None else locale
try:
this_dict = await libbot.json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
try:
this_dict = await libbot.json_read(
Path(f'{locales_root}/{await libbot.config_get("locale")}.json')
)
except FileNotFoundError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
return this_key[key]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
async def in_all_locales(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale")
) -> list:
"""Get value of the provided key and path in all available locales
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `list`: List of values in all locales
"""
output = []
files_locales = listdir(locales_root)
valid_locales = [".".join(entry.split(".")[:-1]) for entry in files_locales]
for lc in valid_locales:
try:
this_dict = await libbot.json_read(Path(f"{locales_root}/{lc}.json"))
except FileNotFoundError:
continue
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output.append(this_key[key])
except KeyError:
continue
return output
async def in_every_locale(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale")
) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value
"""
output = {}
files_locales = listdir(locales_root)
valid_locales = [".".join(entry.split(".")[:-1]) for entry in files_locales]
for lc in valid_locales:
try:
this_dict = await libbot.json_read(Path(f"{locales_root}/{lc}.json"))
except FileNotFoundError:
continue
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output[lc] = this_key[key]
except KeyError:
continue
return output
from ._functions import _, in_all_locales, in_every_locale
from .classes import BotLocale

View File

@@ -0,0 +1,232 @@
from os import listdir, PathLike
from pathlib import Path
from typing import Any, Dict, List
from ..utils.config import config_get
from ..utils.json import json_read
from ..utils.syncs import asyncable
def _get_valid_locales(locales_root: str | PathLike[str]) -> List[str]:
return [".".join(entry.split(".")[:-1]) for entry in listdir(locales_root)]
@asyncable
def _(
key: str,
*args: str,
locale: str | None = "en",
locales_root: str | Path = Path("locale"),
) -> Any:
"""Get value of locale string.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locale (str | None): Locale to looked up in. Defaults to "en".
locales_root (str | Path, optional): Folder where locales are located. Defaults to Path("locale").
Returns:
Any: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`.
"""
if locale is None:
locale: str = config_get("locale")
try:
this_dict: Dict[str, Any] = json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
try:
this_dict: Dict[str, Any] = json_read(Path(f'{locales_root}/{config_get("locale")}.json'))
except FileNotFoundError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
return this_key[key]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
@_.asynchronous
async def _(
key: str,
*args: str,
locale: str | None = "en",
locales_root: str | Path = Path("locale"),
) -> Any:
"""Get value of locale string.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locale (str | None): Locale to looked up in. Defaults to "en".
locales_root (str | Path, optional): Folder where locales are located. Defaults to Path("locale").
Returns:
Any: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`.
"""
locale: str = config_get("locale") if locale is None else locale
try:
this_dict: Dict[str, Any] = await json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
try:
this_dict: Dict[str, Any] = await json_read(
Path(f'{locales_root}/{await config_get("locale")}.json')
)
except FileNotFoundError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
return this_key[key]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
@asyncable
def in_all_locales(key: str, *args: str, locales_root: str | Path = Path("locale")) -> List[Any]:
"""Get value of the provided key and path in all available locales.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locales_root (str | Path, optional): Folder where locales are located. Defaults to `Path("locale")`.
Returns:
List[Any]: List of values in all locales.
"""
output: List[Any] = []
for locale in _get_valid_locales(locales_root):
try:
this_dict: Dict[str, Any] = json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
continue
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output.append(this_key[key])
except KeyError:
continue
return output
@in_all_locales.asynchronous
async def in_all_locales(key: str, *args: str, locales_root: str | Path = Path("locale")) -> List[Any]:
"""Get value of the provided key and path in all available locales.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locales_root (str | Path, optional): Folder where locales are located. Defaults to Path("locale").
Returns:
List[Any]: List of values in all locales.
"""
output: List[Any] = []
for locale in _get_valid_locales(locales_root):
try:
this_dict: Dict[str, Any] = await json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
continue
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output.append(this_key[key])
except KeyError:
continue
return output
@asyncable
def in_every_locale(
key: str, *args: str, locales_root: str | Path = Path("locale")
) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locales_root (str | Path, optional): Folder where locales are located. Defaults to Path("locale").
Returns:
Dict[str, Any]: Locale is a key, and it's value from locale file is a value.
"""
output: Dict[str, Any] = {}
for locale in _get_valid_locales(locales_root):
try:
this_dict: Dict[str, Any] = json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
continue
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output[locale] = this_key[key]
except KeyError:
continue
return output
@in_every_locale.asynchronous
async def in_every_locale(
key: str, *args: str, locales_root: str | Path = Path("locale")
) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locales_root (str | Path, optional): Folder where locales are located. Defaults to Path("locale").
Returns:
Dict[str, Any]: Locale is a key, and it's value from locale file is a value.
"""
output: Dict[str, Any] = {}
for locale in _get_valid_locales(locales_root):
try:
this_dict: Dict[str, Any] = await json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
continue
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output[locale] = this_key[key]
except KeyError:
continue
return output

View File

@@ -0,0 +1 @@
from .bot_locale import BotLocale

View File

@@ -1,8 +1,9 @@
from os import listdir
from pathlib import Path
from typing import Any, Dict, Union
from typing import Any, Dict, List
from libbot import sync
from ...utils.config import config_get
from ...utils.json import json_read
class BotLocale:
@@ -10,52 +11,55 @@ class BotLocale:
def __init__(
self,
default_locale: Union[str, None] = "en",
locales_root: Union[str, Path] = Path("locale"),
default_locale: str | None = "en",
locales_root: str | Path = Path("locale"),
) -> None:
"""
Args:
default_locale (str | None, optional): Default locale. Defaults to "en".
locales_root (str | Path, optional): Path to a directory with locale files. Defaults to Path("locale").
"""
if isinstance(locales_root, str):
locales_root = Path(locales_root)
elif not isinstance(locales_root, Path):
raise TypeError("'locales_root' must be a valid path or path-like object")
files_locales: list = listdir(locales_root)
files_locales: List[str] = listdir(locales_root)
valid_locales: list = [
".".join(entry.split(".")[:-1]) for entry in files_locales
]
valid_locales: List[str] = [".".join(entry.split(".")[:-1]) for entry in files_locales]
self.default: str = (
sync.config_get("locale") if default_locale is None else default_locale
)
self.locales: dict = {}
self.default: str = config_get("locale") if default_locale is None else default_locale
self.locales: Dict[str, Any] = {}
for lc in valid_locales:
self.locales[lc] = sync.json_read(Path(f"{locales_root}/{lc}.json"))
for locale in valid_locales:
self.locales[locale] = json_read(Path(f"{locales_root}/{locale}.json"))
def _(self, key: str, *args: str, locale: Union[str, None] = None) -> Any:
"""Get value of locale string
def _(self, key: str, *args: str, locale: str | None = None) -> Any:
"""Get value of locale string.
### Args:
* key (`str`): The last key of the locale's keys path
* *args (`list`): Path to key like: `dict[args][key]`
* locale (`Union[str, None]`, *optional*): Locale to looked up in. Defaults to config's `"locale"` value
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
locale (str | None, optional): Locale to looked up in. Defaults to config's `"locale"` value.
### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list`
Returns:
Any: Value of provided locale key. Is usually `str`, `Dict[str, Any]` or `List[Any]`.
"""
if locale is None:
locale = self.default
locale: str = self.default
try:
this_dict = self.locales[locale]
this_dict: Dict[str, Any] = self.locales[locale]
except KeyError:
try:
this_dict = self.locales[self.default]
this_dict: Dict[str, Any] = self.locales[self.default]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
return (
f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
)
this_key: Dict[str, Any] = this_dict
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
@@ -64,26 +68,26 @@ class BotLocale:
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
def in_all_locales(self, key: str, *args: str) -> list:
"""Get value of the provided key and path in all available locales
def in_all_locales(self, key: str, *args: str) -> List[Any]:
"""Get value of the provided key and path in all available locales.
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
### Returns:
* `list`: List of values in all locales
Returns:
List[Any]: List of values in all locales.
"""
output: List[Any] = []
output = []
for name, lc in self.locales.items():
for name, locale in self.locales.items():
try:
this_dict = lc
this_dict: Dict[str, Any] = locale
except KeyError:
continue
this_key = this_dict
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]
@@ -95,25 +99,25 @@ class BotLocale:
return output
def in_every_locale(self, key: str, *args: str) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag
"""Get value of the provided key and path in every available locale with locale tag.
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
Args:
key (str): The last key of the locale's keys path.
*args (str): Path to key like: `dict[args][key]`.
### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value
Returns:
Dict[str, Any]: Locale is a key, and it's value from locale file is a value.
"""
output: Dict[str, Any] = {}
output = {}
for name, lc in self.locales.items():
for name, locale in self.locales.items():
try:
this_dict = lc
this_dict: Dict[str, Any] = locale
except KeyError:
continue
this_key = this_dict
this_key: Dict[str, Any] = this_dict
for dict_key in args:
this_key = this_key[dict_key]

View File

@@ -1,117 +0,0 @@
from os import listdir
from pathlib import Path
from typing import Any, Dict, Union
import libbot
def _(
key: str,
*args: str,
locale: Union[str, None] = "en",
locales_root: Union[str, Path] = Path("locale"),
) -> Any:
"""Get value of locale string
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locale (`Union[str, None]`): Locale to looked up in. Defaults to `"en"`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `Any`: Value of provided locale key. Is usually `str`, `dict` or `list`
"""
if locale is None:
locale = libbot.sync.config_get("locale")
try:
this_dict = libbot.sync.json_read(Path(f"{locales_root}/{locale}.json"))
except FileNotFoundError:
try:
this_dict = libbot.sync.json_read(
Path(f'{locales_root}/{libbot.sync.config_get("locale")}.json')
)
except FileNotFoundError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
return this_key[key]
except KeyError:
return f'⚠️ Locale in config is invalid: could not get "{key}" in {args} from locale "{locale}"'
def in_all_locales(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale")
) -> list:
"""Get value of the provided key and path in all available locales
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `list`: List of values in all locales
"""
output = []
files_locales = listdir(locales_root)
valid_locales = [".".join(entry.split(".")[:-1]) for entry in files_locales]
for lc in valid_locales:
try:
this_dict = libbot.sync.json_read(Path(f"{locales_root}/{lc}.json"))
except FileNotFoundError:
continue
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output.append(this_key[key])
except KeyError:
continue
return output
def in_every_locale(
key: str, *args: str, locales_root: Union[str, Path] = Path("locale")
) -> Dict[str, Any]:
"""Get value of the provided key and path in every available locale with locale tag
### Args:
* key (`str`): The last key of the locale's keys path.
* *args (`list`): Path to key like: `dict[args][key]`.
* locales_root (`Union[str, Path]`, *optional*): Folder where locales are located. Defaults to `Path("locale")`.
### Returns:
* `Dict[str, Any]`: Locale is a key and it's value from locale file is a value
"""
output = {}
files_locales = listdir(locales_root)
valid_locales = [".".join(entry.split(".")[:-1]) for entry in files_locales]
for lc in valid_locales:
try:
this_dict = libbot.sync.json_read(Path(f"{locales_root}/{lc}.json"))
except FileNotFoundError:
continue
this_key = this_dict
for dict_key in args:
this_key = this_key[dict_key]
try:
output[lc] = this_key[key]
except KeyError:
continue
return output

View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[pycord] is not installed

View File

@@ -1,47 +1,38 @@
import logging
from logging import Logger
from pathlib import Path
from typing import Any, Dict, List, Union
from typing import Any, Dict
from typing_extensions import override
from ...i18n.classes import BotLocale
from ...utils import json_read
try:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.schedulers.background import BackgroundScheduler
from discord import Bot
except ImportError as exc:
raise ImportError(
"You need to install libbot[pycord] in order to use this class."
) from exc
raise ImportError("You need to install libbot[pycord] in order to use this class.") from exc
try:
from ujson import loads
except ImportError:
from json import loads
from libbot.i18n import BotLocale
from libbot.i18n.sync import _
logger = logging.getLogger(__name__)
logger: Logger = logging.getLogger(__name__)
class PycordBot(Bot):
@override
def __init__(
self,
config: Union[Dict[str, Any], None] = None,
config_path: Union[str, Path] = Path("config.json"),
locales_root: Union[str, Path, None] = None,
scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = None,
*args,
config: Dict[str, Any] | None = None,
config_path: str | Path = Path("config.json"),
locales_root: str | Path | None = None,
scheduler: AsyncIOScheduler | BackgroundScheduler | None = None,
**kwargs,
):
if config is None:
with open(config_path, "r", encoding="utf-8") as f:
self.config: dict = loads(f.read())
else:
self.config = config
self.config: Dict[str, Any] = config if config is not None else json_read(config_path)
super().__init__(
debug_guilds=(
self.config["bot"]["debug_guilds"] if self.config["debug"] else None
),
debug_guilds=(self.config["bot"]["debug_guilds"] if self.config["debug"] else None),
owner_ids=self.config["bot"]["owners"],
*args,
**kwargs,
@@ -58,4 +49,18 @@ class PycordBot(Bot):
self.in_all_locales = self.bot_locale.in_all_locales
self.in_every_locale = self.bot_locale.in_every_locale
self.scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = scheduler
self.scheduler: AsyncIOScheduler | BackgroundScheduler | None = scheduler
@override
async def start(self, token: str, reconnect: bool = True, scheduler_start: bool = True) -> None:
if self.scheduler is not None and scheduler_start:
self.scheduler.start()
await super().start(token, reconnect=reconnect)
@override
async def close(self, scheduler_shutdown: bool = True, scheduler_wait: bool = True) -> None:
if self.scheduler is not None and scheduler_shutdown:
self.scheduler.shutdown(scheduler_wait)
await super().close()

View File

@@ -0,0 +1 @@
from .color import color_from_hex, hex_from_color

View File

@@ -0,0 +1,35 @@
from discord import Colour
def _int_from_hex(hex_string: str) -> int:
try:
return int(hex_string, base=16)
except Exception as exc:
raise ValueError("Input string must be a valid HEX code.") from exc
def _hex_from_int(color_int: int) -> str:
if not 0 <= color_int <= 0xFFFFFF:
raise ValueError("Color's value must be in the range 0 to 0xFFFFFF.")
return f"#{color_int:06x}"
def color_from_hex(hex_string: str) -> Colour:
"""Convert valid hexadecimal string to discord.Colour.
:param hex_string: Hexadecimal string to convert into Colour object
:type hex_string: str
:return: Colour object
"""
return Colour(_int_from_hex(hex_string))
def hex_from_color(color: Colour) -> str:
"""Convert discord.Colour to hexadecimal string.
:param color: Colour object to convert into the string
:type color: Colour
:return: Hexadecimal string in #XXXXXX format
"""
return _hex_from_int(color.value)

View File

@@ -0,0 +1,2 @@
# This file is left empty on purpose
# Adding imports here will cause import errors when libbot[pyrogram] is not installed

View File

@@ -1,10 +1,20 @@
import asyncio
import logging
import sys
from datetime import datetime, timedelta
from logging import Logger
from os import cpu_count, getpid
from pathlib import Path
from time import time
from typing import Any, Dict, List, Union
from typing import Any, Dict, List
from typing_extensions import override
from .command import PyroCommand
from .commandset import CommandSet
from ...i18n import _
from ...i18n.classes import BotLocale
from ...utils import json_read
try:
import pyrogram
@@ -25,75 +35,59 @@ try:
BotCommandScopeDefault,
)
except ImportError as exc:
raise ImportError(
"You need to install libbot[pyrogram] in order to use this class."
) from exc
raise ImportError("You need to install libbot[pyrogram] in order to use this class.") from exc
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
from libbot.i18n import BotLocale
from libbot.i18n.sync import _
from libbot.pyrogram.classes.command import PyroCommand
from libbot.pyrogram.classes.commandset import CommandSet
logger = logging.getLogger(__name__)
logger: Logger = logging.getLogger(__name__)
class PyroClient(Client):
@override
def __init__(
self,
name: str = "bot_client",
owner: Union[int, None] = None,
config: Union[Dict[str, Any], None] = None,
config_path: Union[str, Path] = Path("config.json"),
api_id: Union[int, None] = None,
api_hash: Union[str, None] = None,
bot_token: Union[str, None] = None,
owner: int | None = None,
config: Dict[str, Any] | None = None,
config_path: str | Path = Path("config.json"),
api_id: int | None = None,
api_hash: str | None = None,
bot_token: str | None = None,
workers: int = min(32, cpu_count() + 4),
locales_root: Union[str, Path, None] = None,
locales_root: str | Path | None = None,
plugins_root: str = "plugins",
plugins_exclude: Union[List[str], None] = None,
plugins_exclude: List[str] | None = None,
sleep_threshold: int = 120,
max_concurrent_transmissions: int = 1,
commands_source: Union[Dict[str, dict], None] = None,
scoped_commands: Union[bool, None] = None,
commands_source: Dict[str, dict] | None = None,
scoped_commands: bool | None = None,
i18n_bot_info: bool = False,
scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = None,
scheduler: AsyncIOScheduler | BackgroundScheduler | None = None,
**kwargs,
):
if config is None:
with open(config_path, "r", encoding="utf-8") as f:
self.config: dict = loads(f.read())
else:
self.config = config
self.config: Dict[str, Any] = config if config is not None else json_read(config_path)
super().__init__(
name=name,
api_id=self.config["bot"]["api_id"] if api_id is None else api_id,
api_hash=self.config["bot"]["api_hash"] if api_hash is None else api_hash,
bot_token=self.config["bot"]["bot_token"]
if bot_token is None
else bot_token,
bot_token=self.config["bot"]["bot_token"] if bot_token is None else bot_token,
# Workers should be `min(32, cpu_count() + 4)`, otherwise
# handlers land in another event loop and you won't see them
workers=self.config["bot"]["workers"]
if "workers" in self.config["bot"]
else workers,
workers=self.config["bot"]["workers"] if "workers" in self.config["bot"] else workers,
plugins=dict(
root=plugins_root,
exclude=self.config["disabled_plugins"]
if plugins_exclude is None
else plugins_exclude,
exclude=self.config["disabled_plugins"] if plugins_exclude is None else plugins_exclude,
),
sleep_threshold=sleep_threshold,
max_concurrent_transmissions=self.config["bot"][
"max_concurrent_transmissions"
]
max_concurrent_transmissions=(
self.config["bot"]["max_concurrent_transmissions"]
if "max_concurrent_transmissions" in self.config["bot"]
else max_concurrent_transmissions,
else max_concurrent_transmissions
),
**kwargs,
)
self.owner: int = self.config["bot"]["owner"] if owner is None else owner
@@ -102,9 +96,7 @@ class PyroClient(Client):
self.config["commands"] if commands_source is None else commands_source
)
self.scoped_commands: bool = (
self.config["bot"]["scoped_commands"]
if scoped_commands is None
else scoped_commands
self.config["bot"]["scoped_commands"] if scoped_commands is None else scoped_commands
)
self.start_time: float = 0
@@ -119,13 +111,14 @@ class PyroClient(Client):
self.in_all_locales = self.bot_locale.in_all_locales
self.in_every_locale = self.bot_locale.in_every_locale
self.scheduler: Union[AsyncIOScheduler, BackgroundScheduler, None] = scheduler
self.scheduler: AsyncIOScheduler | BackgroundScheduler | None = scheduler
self.scopes_placeholders: Dict[str, int] = {"owner": self.owner}
self.i18n_bot_info: bool = i18n_bot_info
async def start(self, register_commands: bool = True) -> None:
@override
async def start(self, register_commands: bool = True, scheduler_start: bool = True) -> None:
await super().start()
self.start_time = time()
@@ -178,7 +171,7 @@ class PyroClient(Client):
)
logger.info(
"Bot's info for the locale %s has been updated",
self.code,
code,
)
except KeyError:
logger.warning(
@@ -189,9 +182,11 @@ class PyroClient(Client):
# Send a message to the bot's reports chat about the startup
try:
await self.send_message(
chat_id=self.owner
chat_id=(
self.owner
if self.config["reports"]["chat_id"] == "owner"
else self.config["reports"]["chat_id"],
else self.config["reports"]["chat_id"]
),
text=f"Bot started PID `{getpid()}`",
)
except BadRequest:
@@ -209,32 +204,39 @@ class PyroClient(Client):
kwargs={"command_sets": await self.collect_commands()},
)
if scheduler_start:
self.scheduler.start()
async def stop(self, exit_completely: bool = True) -> None:
@override
async def stop(
self, exit_completely: bool = True, scheduler_shutdown: bool = True, scheduler_wait: bool = True
) -> None:
try:
await self.send_message(
chat_id=self.owner
chat_id=(
self.owner
if self.config["reports"]["chat_id"] == "owner"
else self.config["reports"]["chat_id"],
else self.config["reports"]["chat_id"]
),
text=f"Bot stopped with PID `{getpid()}`",
)
await asyncio.sleep(0.5)
except BadRequest:
logger.warning("Unable to send message to report chat.")
if self.scheduler is not None and scheduler_shutdown:
self.scheduler.shutdown(scheduler_wait)
await super().stop()
logger.warning("Bot stopped with PID %s.", getpid())
if exit_completely:
try:
exit()
sys.exit()
except SystemExit as exc:
raise SystemExit(
"Bot has been shut down, this is not an application error!"
) from exc
raise SystemExit("Bot has been shut down, this is not an application error!") from exc
async def collect_commands(self) -> Union[List[CommandSet], None]:
async def collect_commands(self) -> List[CommandSet] | None:
"""Gather list of the bot's commands
### Returns:
@@ -256,13 +258,9 @@ class PyroClient(Client):
scopes[dumps(scope)] = {"_": []}
# Add command to the scope's flattened key in scopes dict
scopes[dumps(scope)]["_"].append(
BotCommand(command, _(command, "commands"))
)
scopes[dumps(scope)]["_"].append(BotCommand(command, _(command, "commands")))
for locale, string in (
self.in_every_locale(command, "commands")
).items():
for locale, string in (self.in_every_locale(command, "commands")).items():
if locale not in scopes[dumps(scope)]:
scopes[dumps(scope)][locale] = []
@@ -281,11 +279,7 @@ class PyroClient(Client):
# Create object with the same name and args from the dict
try:
scope_obj = globals()[scope_dict["name"]](
**{
key: value
for key, value in scope_dict.items()
if key != "name"
}
**{key: value for key, value in scope_dict.items() if key != "name"}
)
except NameError:
logger.error(
@@ -303,13 +297,9 @@ class PyroClient(Client):
# Add set of commands to the list of the command sets
for locale, commands in locales.items():
if locale == "_":
command_sets.append(
CommandSet(commands, scope=scope_obj, language_code="")
)
command_sets.append(CommandSet(commands, scope=scope_obj, language_code=""))
continue
command_sets.append(
CommandSet(commands, scope=scope_obj, language_code=locale)
)
command_sets.append(CommandSet(commands, scope=scope_obj, language_code=locale))
logger.info("Registering the following command sets: %s", command_sets)
@@ -317,7 +307,9 @@ class PyroClient(Client):
# This part here looks into the handlers and looks for commands
# in it, if there are any. Then adds them to self.commands
for handler in self.dispatcher.groups[0]:
if isinstance(handler, MessageHandler):
if isinstance(handler, MessageHandler) and (
hasattr(handler.filters, "base") or hasattr(handler.filters, "other")
):
for entry in [handler.filters.base, handler.filters.other]:
if hasattr(entry, "commands"):
for command in entry.commands:
@@ -346,9 +338,7 @@ class PyroClient(Client):
command,
)
async def register_commands(
self, command_sets: Union[List[CommandSet], None] = None
) -> None:
async def register_commands(self, command_sets: List[CommandSet] | None = None) -> None:
"""Register commands stored in bot's 'commands' attribute"""
if command_sets is None:
@@ -357,9 +347,7 @@ class PyroClient(Client):
for command in self.commands
]
logger.info(
"Registering commands %s with a default scope 'BotCommandScopeDefault'"
)
logger.info("Registering commands %s with a default scope 'BotCommandScopeDefault'", commands)
await self.set_bot_commands(commands)
return
@@ -377,15 +365,11 @@ class PyroClient(Client):
language_code=command_set.language_code,
)
async def remove_commands(
self, command_sets: Union[List[CommandSet], None] = None
) -> None:
async def remove_commands(self, command_sets: List[CommandSet] | None = None) -> None:
"""Remove commands stored in bot's 'commands' attribute"""
if command_sets is None:
logger.info(
"Removing commands with a default scope 'BotCommandScopeDefault'"
)
logger.info("Removing commands with a default scope 'BotCommandScopeDefault'")
await self.delete_bot_commands(BotCommandScopeDefault())
return

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List, Union
from typing import List
try:
from pyrogram.types import (
@@ -13,9 +13,7 @@ try:
BotCommandScopeDefault,
)
except ImportError as exc:
raise ImportError(
"You need to install libbot[pyrogram] in order to use this class."
) from exc
raise ImportError("You need to install libbot[pyrogram] in order to use this class.") from exc
@dataclass
@@ -23,13 +21,13 @@ class CommandSet:
"""Command stored in PyroClient's 'commands' attribute"""
commands: List[BotCommand]
scope: Union[
BotCommandScopeDefault,
BotCommandScopeAllPrivateChats,
BotCommandScopeAllGroupChats,
BotCommandScopeAllChatAdministrators,
BotCommandScopeChat,
BotCommandScopeChatAdministrators,
BotCommandScopeChatMember,
] = BotCommandScopeDefault
scope: (
BotCommandScopeDefault
| BotCommandScopeAllPrivateChats
| BotCommandScopeAllGroupChats
| BotCommandScopeAllChatAdministrators
| BotCommandScopeChat
| BotCommandScopeChatAdministrators
| BotCommandScopeChatMember
) = BotCommandScopeDefault
language_code: str = ""

View File

@@ -1,116 +0,0 @@
from pathlib import Path
from typing import Any, Union
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
def json_read(path: Union[str, Path]) -> Any:
"""Read contents of a JSON file
### Args:
* path (`Union[str, Path]`): Path-like object or path as a string
### Returns:
* `Any`: File contents
"""
with open(str(path), mode="r", encoding="utf-8") as f:
data = f.read()
return loads(data)
def json_write(data: Any, path: Union[str, Path]) -> None:
"""Write contents to a JSON file
### Args:
* data (`Any`): Contents to write. Must be a JSON serializable
* path (`Union[str, Path]`): Path-like object or path as a string of a destination
"""
with open(str(path), mode="w", encoding="utf-8") as f:
f.write(
dumps(data, ensure_ascii=False, escape_forward_slashes=False, indent=4)
if hasattr(dumps, "escape_forward_slashes")
else dumps(data, ensure_ascii=False, indent=4)
)
def nested_set(target: dict, value: Any, *path: str, create_missing=True) -> dict:
"""Set the key by its path to the value
### Args:
* target (`dict`): Dictionary to perform modifications on
* value (`Any`): Any data
* *path (`str`): Path to the key of the target
* create_missing (`bool`, *optional*): Create keys on the way if they're missing. Defaults to `True`
### Returns:
* `dict`: Changed dictionary
"""
d = target
for key in path[:-1]:
if key in d:
d = d[key]
elif create_missing:
d = d.setdefault(key, {})
else:
raise KeyError(
f"Key '{key}' is not found under path provided ({path}) and create_missing is False"
)
if path[-1] in d or create_missing:
d[path[-1]] = value
return target
def config_get(
key: str, *path: str, config_file: Union[str, Path] = "config.json"
) -> Any:
"""Get a value of the config key by its path provided
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`
### Args:
* key (`str`): Key that contains the value
* *path (`str`): Path to the key that contains the value
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
### Returns:
* `Any`: Key's value
### Example:
Get the "salary" of "Pete" from this JSON structure:
```json
{
"users": {
"Pete": {
"salary": 10.0
}
}
}
```
This can be easily done with the following code:
```python
import libbot
salary = libbot.sync.config_get("salary", "users", "Pete")
```
"""
this_key = json_read(config_file)
for dict_key in path:
this_key = this_key[dict_key]
return this_key[key]
def config_set(
key: str, value: Any, *path: str, config_file: Union[str, Path] = "config.json"
) -> None:
"""Set config's key by its path to the value
### Args:
* key (`str`): Key that leads to the value
* value (`Any`): Any JSON serializable data
* *path (`str`): Path to the key of the target
* config_file (`Union[str, Path]`, *optional*): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
"""
json_write(nested_set(json_read(config_file), value, *(*path, key)), config_file)
return

View File

@@ -0,0 +1,3 @@
from . import misc
from .config import config_get, config_set, config_delete
from .json import json_read, json_write

View File

@@ -0,0 +1 @@
from ._functions import config_get, config_set, config_delete

View File

@@ -0,0 +1,163 @@
from pathlib import Path
from typing import Any, Dict
from ..json import json_read, json_write
from ..misc import nested_delete, nested_set
from ..syncs import asyncable
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
DEFAULT_CONFIG_LOCATION: str = "config.json"
@asyncable
def config_get(key: str, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> Any:
"""Get a value of the config key by its path provided.
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`.
Args:
key (str): Key that contains the value
*path (str): Path to the key that contains the value (pass *[] or don't pass anything at all to get on the top/root level)
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
Returns:
Any: Key's value
Example:
Get the "salary" of "Pete" from this JSON structure: `{"users": {"Pete": {"salary": 10.0}}}`
This can be easily done with the following code:
>>> import libbot
salary: float = libbot.sync.config_get("salary", "users", "Pete")
"""
this_key: Dict[str, Any] = json_read(config_file)
for dict_key in path:
this_key = this_key[dict_key]
return this_key[key]
@config_get.asynchronous
async def config_get(key: str, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> Any:
"""Get a value of the config key by its path provided.
For example, `foo.bar.key` has a path of `"foo", "bar"` and the key `"key"`.
Args:
key (str): Key that contains the value
*path (str): Path to the key that contains the value (pass *[] or don't pass anything at all to get on the top/root level)
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to `"config.json"`
Returns:
Any: Key's value
Example:
Get the "salary" of "Pete" from this JSON structure: `{"users": {"Pete": {"salary": 10.0}}}`
This can be easily done with the following code:
>>> import libbot
salary: float = libbot.sync.config_get("salary", "users", "Pete")
"""
this_key: Dict[str, Any] = await json_read(config_file)
for dict_key in path:
this_key = this_key[dict_key]
return this_key[key]
@asyncable
def config_set(key: str, value: Any, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION) -> None:
"""Set config's key by its path to the value.
Args:
key (str): Key that leads to the value.
value (Any): Any JSON-serializable data.
*path (str): Path to the key of the target (pass *[] or don't pass anything at all to set on the top/root level).
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to "config.json".
Raises:
KeyError: Key was not found under the provided path.
"""
json_write(nested_set(json_read(config_file), value, *(*path, key)), config_file)
@config_set.asynchronous
async def config_set(
key: str, value: Any, *path: str, config_file: str | Path = DEFAULT_CONFIG_LOCATION
) -> None:
"""Set config's key by its path to the value.
Args:
key (str): Key that leads to the value.
value (Any): Any JSON-serializable data.
*path (str): Path to the key of the target (pass *[] or don't pass anything at all to set on the top/root level).
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to "config.json".
Raises:
KeyError: Key was not found under the provided path.
"""
await json_write(nested_set(await json_read(config_file), value, *(*path, key)), config_file)
@asyncable
def config_delete(
key: str,
*path: str,
missing_ok: bool = False,
config_file: str | Path = DEFAULT_CONFIG_LOCATION,
) -> None:
"""Delete config's key by its path.
Args:
key (str): Key to delete.
*path (str): Path to the key of the target (pass *[] or don't pass anything at all to delete on the top/root level)
missing_ok (bool): Do not raise an exception if the key is missing. Defaults to False.
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to "config.json".
Raises:
KeyError: Key is not found under path provided and `missing_ok` is False.
"""
config_data: Dict[str, Any] = json_read(config_file)
try:
nested_delete(config_data, *(*path, key))
except KeyError as exc:
if not missing_ok:
raise exc from exc
json_write(config_data, config_file)
@config_delete.asynchronous
async def config_delete(
key: str,
*path: str,
missing_ok: bool = False,
config_file: str | Path = DEFAULT_CONFIG_LOCATION,
) -> None:
"""Delete config's key by its path.
Args:
key (str): Key to delete.
*path (str): Path to the key of the target (pass *[] or don't pass anything at all to delete on the top/root level)
missing_ok (bool): Do not raise an exception if the key is missing. Defaults to False.
config_file (str | Path, optional): Path-like object or path as a string of a location of the config file. Defaults to "config.json".
Raises:
KeyError: Key is not found under path provided and `missing_ok` is False.
"""
config_data: Dict[str, Any] = await json_read(config_file)
try:
nested_delete(config_data, *(*path, key))
except KeyError as exc:
if not missing_ok:
raise exc from exc
await json_write(config_data, config_file)

View File

@@ -0,0 +1 @@
from ._functions import json_read, json_write

View File

@@ -0,0 +1,76 @@
from pathlib import Path
from typing import Any
import aiofiles
from ..misc import supports_argument
from ..syncs import asyncable
try:
from ujson import dumps, loads
except ImportError:
from json import dumps, loads
@asyncable
def json_read(path: str | Path) -> Any:
"""Read contents of a JSON file and return it.
Args:
path (str | Path): Path-like object or path to the file as a string.
Returns:
Any: File contents.
"""
with open(str(path), mode="r", encoding="utf-8") as f:
data = f.read()
return loads(data)
@json_read.asynchronous
async def json_read(path: str | Path) -> Any:
"""Read contents of a JSON file and return it.
Args:
path (str | Path): Path-like object or path to the file as a string.
Returns:
Any: File contents.
"""
async with aiofiles.open(str(path), mode="r", encoding="utf-8") as f:
data = await f.read()
return loads(data)
@asyncable
def json_write(data: Any, path: str | Path) -> None:
"""Write contents to a JSON file.
Args:
data (Any): Contents to write. Must be a JSON-serializable object.
path (str | Path): Path-like object or path to the file as a string.
"""
with open(str(path), mode="w", encoding="utf-8") as f:
f.write(
dumps(data, ensure_ascii=False, escape_forward_slashes=False, indent=4)
if supports_argument(dumps, "escape_forward_slashes")
else dumps(data, ensure_ascii=False, indent=4)
)
@json_write.asynchronous
async def json_write(data: Any, path: str | Path) -> None:
"""Write contents to a JSON file.
Args:
data (Any): Contents to write. Must be a JSON-serializable object.
path (str | Path): Path-like object or path to the file as a string.
"""
async with aiofiles.open(str(path), mode="w", encoding="utf-8") as f:
await f.write(
dumps(data, ensure_ascii=False, escape_forward_slashes=False, indent=4)
if supports_argument(dumps, "escape_forward_slashes")
else dumps(data, ensure_ascii=False, indent=4)
)

View File

@@ -0,0 +1 @@
from ._functions import supports_argument, nested_set, nested_delete

View File

@@ -0,0 +1,87 @@
import inspect
from typing import Any, Dict
from typing import Callable
def supports_argument(func: Callable[..., Any], arg_name: str) -> bool:
"""Check whether a function has a specific argument.
Args:
func (Callable[..., Any]): Function to be inspected.
arg_name (str): Argument to be checked.
Returns:
bool: True if argument is supported and False if not.
"""
if hasattr(func, "__code__"):
return arg_name in inspect.signature(func).parameters
if hasattr(func, "__doc__"):
if doc := func.__doc__:
first_line = doc.splitlines()[0]
return arg_name in first_line
return False
def nested_set(
target: Dict[str, Any], value: Any, *path: str, create_missing: bool = True
) -> Dict[str, Any]:
"""Set the key by its path to the value
Args:
target (Dict[str, Any]): Dictionary to perform the modification on.
value (Any): New value.
*path (str): Path to the key.
create_missing (:obj:`bool`, optional): Create keys on the way if they're missing. Defaults to True.
Raises:
KeyError: Key is not found under the provided path.
Returns:
Dict[str, Any]: Modified dictionary.
"""
target_copy: Dict[str, Any] = target
for key in path[:-1]:
if key in target_copy:
target_copy = target_copy[key]
elif create_missing:
target_copy = target_copy.setdefault(key, {})
else:
raise KeyError(
f"Key '{key}' is not found under path provided ({path}) and create_missing is False"
)
if path[-1] in target_copy or create_missing:
target_copy[path[-1]] = value
return target
def nested_delete(target: Dict[str, Any], *path: str) -> Dict[str, Any]:
"""Delete the key by its path.
Args:
target (Dict[str, Any]): Dictionary to perform the modification on.
Raises:
KeyError: Key is not found under the provided path.
Returns:
Dict[str, Any]: Modified dictionary.
"""
target_copy: Dict[str, Any] = target
for key in path[:-1]:
if key in target_copy:
target_copy = target_copy[key]
else:
raise KeyError(f"Key '{key}' is not found under path provided ({path})")
if path[-1] in target_copy:
del target_copy[path[-1]]
else:
raise KeyError(f"Key '{path[-1]}' is not found under path provided ({path})")
return target

View File

@@ -0,0 +1 @@
from ._syncs import asyncable

View File

@@ -0,0 +1,69 @@
import asyncio
import inspect
from inspect import FrameInfo
from typing import Any, Callable, Optional, Type
class asyncable:
"""Allows to mark a callable able to be async.
Source: https://itsjohannawren.medium.com/single-call-sync-and-async-in-python-2acadd07c9d6"""
def __init__(self, method: Callable):
self.__sync = method
self.__async = None
def asynchronous(self, method: Callable) -> "asyncable":
if not isinstance(method, Callable):
raise RuntimeError("NOT CALLABLE!!!")
self.__async = method
return self
@staticmethod
def __is_awaited() -> bool:
frame: FrameInfo = inspect.stack()[2]
if not hasattr(frame, "positions"):
return False
return (
frame.positions.col_offset >= 6
and frame.code_context[frame.index][frame.positions.col_offset - 6 : frame.positions.col_offset]
== "await "
)
def __get__(
self,
instance: Type,
*args,
owner_class: Optional[Type[Type]] = None,
**kwargs,
) -> Callable:
if self.__is_awaited():
if self.__async is None:
raise RuntimeError(
"Attempting to call asyncable with await, but no asynchronous call has been defined"
)
bound_method = self.__async.__get__(instance, owner_class)
if isinstance(self.__sync, classmethod):
return lambda: asyncio.ensure_future(bound_method(owner_class, *args, **kwargs))
return lambda: asyncio.ensure_future(bound_method(*args, **kwargs))
bound_method = self.__sync.__get__(instance, owner_class)
return lambda: bound_method(*args, **kwargs)
def __call__(self, *args, **kwargs) -> Any:
if self.__is_awaited():
if self.__async is None:
raise RuntimeError(
"Attempting to call asyncable with await, but no asynchronous call has been defined"
)
return asyncio.ensure_future(self.__async(*args, **kwargs))
return self.__sync(*args, **kwargs)

View File

@@ -2,5 +2,14 @@
"locale": "en",
"bot": {
"bot_token": "sample_token"
},
"cache": {
"type": "memcached",
"memcached": {
"uri": "127.0.0.1:11211"
},
"redis": {
"uri": "redis://127.0.0.1:6379/0"
}
}
}

View File

@@ -1,4 +1,7 @@
from json import dumps, loads
from os import makedirs
from pathlib import Path
from uuid import uuid4
import pytest
@@ -7,7 +10,22 @@ from libbot.i18n import BotLocale
@pytest.fixture()
def location_config() -> Path:
return Path("tests/config.json")
makedirs(Path("tests/.tmp"), exist_ok=True)
filename = str(uuid4())
with open(Path("tests/config.json"), "r", encoding="utf-8") as file:
config = loads(file.read())
with open(Path(f"tests/.tmp/{filename}.json"), "w", encoding="utf-8") as file:
file.write(
dumps(
config,
ensure_ascii=False,
indent=4,
)
)
return Path(f"tests/.tmp/{filename}.json")
@pytest.fixture()

View File

@@ -1,8 +1,6 @@
from pathlib import Path
from typing import Any, List, Union
from typing import Any, List
import pytest
from libbot.i18n import BotLocale
@@ -20,14 +18,12 @@ from libbot.i18n import BotLocale
def test_bot_locale_get(
key: str,
args: List[str],
locale: Union[str, None],
locale: str | None,
expected: Any,
bot_locale: BotLocale,
):
assert (
bot_locale._(key, *args, locale=locale)
if locale is not None
else bot_locale._(key, *args)
bot_locale._(key, *args, locale=locale) if locale is not None else bot_locale._(key, *args)
) == expected
@@ -39,9 +35,7 @@ def test_bot_locale_get(
("nested", ["callbacks", "default"], ["sure", "авжеж"]),
],
)
def test_i18n_in_all_locales(
key: str, args: List[str], expected: Any, bot_locale: BotLocale
):
def test_i18n_in_all_locales(key: str, args: List[str], expected: Any, bot_locale: BotLocale):
assert (bot_locale.in_all_locales(key, *args)) == expected
@@ -53,7 +47,5 @@ def test_i18n_in_all_locales(
("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}),
],
)
def test_i18n_in_every_locale(
key: str, args: List[str], expected: Any, bot_locale: BotLocale
):
def test_i18n_in_every_locale(key: str, args: List[str], expected: Any, bot_locale: BotLocale):
assert (bot_locale.in_every_locale(key, *args)) == expected

28
tests/test_cache.py Normal file
View File

@@ -0,0 +1,28 @@
from pathlib import Path
from libbot.cache.classes import Cache
from libbot.cache.manager import create_cache_client
try:
from ujson import JSONDecodeError, dumps, loads
except ImportError:
from json import JSONDecodeError, dumps, loads
from typing import Any, Dict
import pytest
@pytest.mark.parametrize(
"engine",
[
"memcached",
"redis",
],
)
def test_cache_creation(engine: str, location_config: Path):
with open(location_config, "r", encoding="utf-8") as file:
config: Dict[str, Any] = loads(file.read())
cache: Cache = create_cache_client(config, engine)
assert isinstance(cache, Cache)

View File

@@ -2,8 +2,7 @@ from pathlib import Path
from typing import Any, List
import pytest
from libbot import config_get, config_set
from libbot.utils import config_delete, config_get, config_set
@pytest.mark.asyncio
@@ -25,14 +24,9 @@ async def test_config_get_valid(args: List[str], expected: str, location_config:
(["bot_stonks", "bot"], pytest.raises(KeyError)),
],
)
async def test_config_get_invalid(
args: List[str], expected: Any, location_config: Path
):
async def test_config_get_invalid(args: List[str], expected: Any, location_config: Path):
with expected:
assert (
await config_get(args[0], *args[1:], config_file=location_config)
== expected
)
assert await config_get(args[0], *args[1:], config_file=location_config) == expected
@pytest.mark.asyncio
@@ -46,3 +40,26 @@ async def test_config_get_invalid(
async def test_config_set(key: str, path: List[str], value: Any, location_config: Path):
await config_set(key, value, *path, config_file=location_config)
assert await config_get(key, *path, config_file=location_config) == value
@pytest.mark.asyncio
@pytest.mark.parametrize(
"key, path",
[
("bot_token", ["bot"]),
],
)
async def test_config_delete(key: str, path: List[str], location_config: Path):
await config_delete(key, *path, config_file=location_config)
assert key not in (await config_get(*path, config_file=location_config))
@pytest.mark.asyncio
@pytest.mark.parametrize(
"key, path",
[
("bot_lol", ["bot"]),
],
)
async def test_config_delete_missing(key: str, path: List[str], location_config: Path):
assert await config_delete(key, *path, missing_ok=True, config_file=location_config) is None

View File

@@ -2,8 +2,7 @@ from pathlib import Path
from typing import Any, List
import pytest
from libbot import sync
from libbot.utils import config_delete, config_get, config_set
@pytest.mark.parametrize(
@@ -13,8 +12,8 @@ from libbot import sync
(["bot_token", "bot"], "sample_token"),
],
)
def test_config_get_valid(args: List[str], expected: str, location_config: Path):
assert sync.config_get(args[0], *args[1:], config_file=location_config) == expected
async def test_config_get_valid(args: List[str], expected: str, location_config: Path):
assert config_get(args[0], *args[1:], config_file=location_config) == expected
@pytest.mark.parametrize(
@@ -25,9 +24,7 @@ def test_config_get_valid(args: List[str], expected: str, location_config: Path)
)
def test_config_get_invalid(args: List[str], expected: Any, location_config: Path):
with expected:
assert (
sync.config_get(args[0], *args[1:], config_file=location_config) == expected
)
assert config_get(args[0], *args[1:], config_file=location_config) == expected
@pytest.mark.parametrize(
@@ -38,5 +35,26 @@ def test_config_get_invalid(args: List[str], expected: Any, location_config: Pat
],
)
def test_config_set(key: str, path: List[str], value: Any, location_config: Path):
sync.config_set(key, value, *path, config_file=location_config)
assert sync.config_get(key, *path, config_file=location_config) == value
config_set(key, value, *path, config_file=location_config)
assert config_get(key, *path, config_file=location_config) == value
@pytest.mark.parametrize(
"key, path",
[
("bot_token", ["bot"]),
],
)
def test_config_delete(key: str, path: List[str], location_config: Path):
config_delete(key, *path, config_file=location_config)
assert key not in config_get(*path, config_file=location_config)
@pytest.mark.parametrize(
"key, path",
[
("bot_lol", ["bot"]),
],
)
def test_config_delete_missing(key: str, path: List[str], location_config: Path):
assert config_delete(key, *path, missing_ok=True, config_file=location_config) is None

View File

@@ -1,8 +1,7 @@
from pathlib import Path
from typing import Any, List, Union
from typing import Any, List
import pytest
from libbot import i18n
@@ -21,7 +20,7 @@ from libbot import i18n
async def test_i18n_get(
key: str,
args: List[str],
locale: Union[str, None],
locale: str | None,
expected: Any,
location_locale: Path,
):
@@ -41,12 +40,8 @@ async def test_i18n_get(
("nested", ["callbacks", "default"], ["sure", "авжеж"]),
],
)
async def test_i18n_in_all_locales(
key: str, args: List[str], expected: Any, location_locale: Path
):
assert (
await i18n.in_all_locales(key, *args, locales_root=location_locale)
) == expected
async def test_i18n_in_all_locales(key: str, args: List[str], expected: Any, location_locale: Path):
assert (await i18n.in_all_locales(key, *args, locales_root=location_locale)) == expected
@pytest.mark.asyncio
@@ -58,9 +53,5 @@ async def test_i18n_in_all_locales(
("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}),
],
)
async def test_i18n_in_every_locale(
key: str, args: List[str], expected: Any, location_locale: Path
):
assert (
await i18n.in_every_locale(key, *args, locales_root=location_locale)
) == expected
async def test_i18n_in_every_locale(key: str, args: List[str], expected: Any, location_locale: Path):
assert (await i18n.in_every_locale(key, *args, locales_root=location_locale)) == expected

View File

@@ -1,9 +1,8 @@
from pathlib import Path
from typing import Any, List, Union
from typing import Any, List
import pytest
from libbot.i18n import sync
from libbot.i18n import _, in_all_locales, in_every_locale
@pytest.mark.parametrize(
@@ -20,14 +19,14 @@ from libbot.i18n import sync
def test_i18n_get(
key: str,
args: List[str],
locale: Union[str, None],
locale: str | None,
expected: Any,
location_locale: Path,
):
assert (
sync._(key, *args, locale=locale, locales_root=location_locale)
_(key, *args, locale=locale, locales_root=location_locale)
if locale is not None
else sync._(key, *args, locales_root=location_locale)
else _(key, *args, locales_root=location_locale)
) == expected
@@ -39,10 +38,8 @@ def test_i18n_get(
("nested", ["callbacks", "default"], ["sure", "авжеж"]),
],
)
def test_i18n_in_all_locales(
key: str, args: List[str], expected: Any, location_locale: Path
):
assert (sync.in_all_locales(key, *args, locales_root=location_locale)) == expected
def test_i18n_in_all_locales(key: str, args: List[str], expected: Any, location_locale: Path):
assert (in_all_locales(key, *args, locales_root=location_locale)) == expected
@pytest.mark.parametrize(
@@ -53,7 +50,5 @@ def test_i18n_in_all_locales(
("nested", ["callbacks", "default"], {"en": "sure", "uk": "авжеж"}),
],
)
def test_i18n_in_every_locale(
key: str, args: List[str], expected: Any, location_locale: Path
):
assert (sync.in_every_locale(key, *args, locales_root=location_locale)) == expected
def test_i18n_in_every_locale(key: str, args: List[str], expected: Any, location_locale: Path):
assert (in_every_locale(key, *args, locales_root=location_locale)) == expected

View File

@@ -4,11 +4,10 @@ except ImportError:
from json import dumps, JSONDecodeError
from pathlib import Path
from typing import Any, Union
from typing import Any
import pytest
from libbot import json_read, json_write
from libbot.utils import json_read, json_write
@pytest.mark.asyncio
@@ -25,7 +24,7 @@ from libbot import json_read, json_write
("tests/data/empty.json", {}),
],
)
async def test_json_read_valid(path: Union[str, Path], expected: Any):
async def test_json_read_valid(path: str | Path, expected: Any):
assert await json_read(path) == expected
@@ -37,7 +36,7 @@ async def test_json_read_valid(path: Union[str, Path], expected: Any):
("tests/data/nonexistent.json", FileNotFoundError),
],
)
async def test_json_read_invalid(path: Union[str, Path], expected: Any):
async def test_json_read_invalid(path: str | Path, expected: Any):
with pytest.raises(expected):
await json_read(path)
@@ -56,7 +55,7 @@ async def test_json_read_invalid(path: Union[str, Path], expected: Any):
({}, "tests/data/empty.json"),
],
)
async def test_json_write(data: Any, path: Union[str, Path]):
async def test_json_write(data: Any, path: str | Path):
await json_write(data, path)
assert Path(path).is_file()

View File

@@ -4,11 +4,10 @@ except ImportError:
from json import dumps, JSONDecodeError
from pathlib import Path
from typing import Any, Union
from typing import Any
import pytest
from libbot import sync
from libbot.utils import json_read, json_write
@pytest.mark.parametrize(
@@ -24,8 +23,8 @@ from libbot import sync
("tests/data/empty.json", {}),
],
)
def test_json_read_valid(path: Union[str, Path], expected: Any):
assert sync.json_read(path) == expected
def test_json_read_valid(path: str | Path, expected: Any):
assert json_read(path) == expected
@pytest.mark.parametrize(
@@ -35,9 +34,9 @@ def test_json_read_valid(path: Union[str, Path], expected: Any):
("tests/data/nonexistent.json", FileNotFoundError),
],
)
def test_json_read_invalid(path: Union[str, Path], expected: Any):
def test_json_read_invalid(path: str | Path, expected: Any):
with pytest.raises(expected):
assert sync.json_read(path) == expected
assert json_read(path) == expected
@pytest.mark.parametrize(
@@ -53,8 +52,8 @@ def test_json_read_invalid(path: Union[str, Path], expected: Any):
({}, "tests/data/empty.json"),
],
)
def test_json_write(data: Any, path: Union[str, Path]):
sync.json_write(data, path)
def test_json_write(data: Any, path: str | Path):
json_write(data, path)
assert Path(path).is_file()
with open(path, "r", encoding="utf-8") as f:

View File

@@ -1,8 +1,7 @@
from typing import Any, Dict, List
import pytest
from libbot import sync
from libbot.utils.misc import nested_delete, nested_set
@pytest.mark.parametrize(
@@ -33,9 +32,7 @@ def test_nested_set_valid(
create_missing: bool,
expected: Any,
):
assert (
sync.nested_set(target, value, *path, create_missing=create_missing)
) == expected
assert (nested_set(target, value, *path, create_missing=create_missing)) == expected
@pytest.mark.parametrize(
@@ -58,6 +55,24 @@ def test_nested_set_invalid(
expected: Any,
):
with pytest.raises(expected):
assert (
sync.nested_set(target, value, *path, create_missing=create_missing)
) == expected
assert (nested_set(target, value, *path, create_missing=create_missing)) == expected
@pytest.mark.parametrize(
"target, path, expected",
[
({"foo": "bar"}, ["foo"], {}),
({"foo": "bar", "bar": "foo"}, ["bar"], {"foo": "bar"}),
(
{"foo": {"bar": {}}},
["foo", "bar"],
{"foo": {}},
),
],
)
def test_nested_delete(
target: Dict[str, Any],
path: List[str],
expected: Any,
):
assert (nested_delete(target, *path)) == expected

25
tests/test_utils.py Normal file
View File

@@ -0,0 +1,25 @@
from typing import Callable
import pytest
from libbot.utils.misc import supports_argument
def func1(foo: str, bar: str):
"""Dummy function with specific arguments"""
pass
def func2(foo: str):
"""Dummy function with specific arguments"""
pass
@pytest.mark.parametrize(
"func, arg_name, result",
[
(func1, "foo", True),
(func2, "bar", False),
],
)
def test_supports_argument(func: Callable, arg_name: str, result: bool):
assert supports_argument(func, arg_name) == result

10
tox.ini
View File

@@ -1,14 +1,13 @@
[tox]
minversion = 3.8.0
envlist = py38, py39, py310, py311
minversion = 3.11.0
envlist = py311, py312, py313
isolated_build = true
[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313
[testenv]
setenv =
@@ -19,5 +18,6 @@ deps =
-r{toxinidir}/requirements/pycord.txt
-r{toxinidir}/requirements/pyrogram.txt
-r{toxinidir}/requirements/speed.txt
-r{toxinidir}/requirements/cache.txt
commands =
pytest --basetemp={envtmpdir} --cov=libbot