Compare commits

..
Author SHA1 Message Date
edraft 5951c46df9 Fixed test action installation
Test before pr merge / test-lint (pull_request) Successful in 7s
Test before pr merge / test (pull_request) Failing after 17s
2026-01-16 16:11:45 +01:00
edraft c18777656c Added test action
Test before pr merge / test (pull_request) Failing after 4s
Test before pr merge / test-lint (pull_request) Successful in 15s
2026-01-16 16:07:09 +01:00
edraft c8de1284fb Fixed formatting
Test before pr merge / test-lint (pull_request) Successful in 7s
2026-01-16 15:55:28 +01:00
edraft 17408d5cd2 Added first tests #191
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-12-10 23:21:52 +01:00
edraft cc76227199 Moved hosted service base
Build on push / prepare (push) Successful in 13s
Build on push / query (push) Successful in 24s
Build on push / core (push) Successful in 25s
Build on push / dependency (push) Successful in 18s
Build on push / cli (push) Successful in 24s
Build on push / application (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / database (push) Successful in 23s
Build on push / translation (push) Successful in 26s
Build on push / auth (push) Successful in 17s
Build on push / api (push) Successful in 22s
Test before pr merge / test-lint (pull_request) Successful in 13s
2025-10-22 11:40:25 +02:00
edraft dfbb0a8c1f Updated cpl structure model json handling
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 12s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 19s
Build on push / cli (push) Successful in 19s
Build on push / dependency (push) Successful in 19s
Build on push / database (push) Successful in 16s
Build on push / application (push) Successful in 17s
Build on push / translation (push) Successful in 19s
Build on push / mail (push) Successful in 22s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
2025-10-19 20:17:17 +02:00
edraft 472aba5990 Removed ws with_parents
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 22s
Build on push / cli (push) Successful in 16s
Build on push / dependency (push) Successful in 19s
Build on push / mail (push) Successful in 17s
Build on push / application (push) Successful in 21s
Build on push / translation (push) Successful in 20s
Build on push / database (push) Successful in 21s
Build on push / auth (push) Successful in 19s
Build on push / api (push) Successful in 15s
2025-10-19 20:03:43 +02:00
twente 9c75008c9f Add cpl-mail project
Test before pr merge / test-lint (pull_request) Successful in 8s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 19s
Build on push / dependency (push) Successful in 19s
Build on push / cli (push) Successful in 24s
Build on push / mail (push) Successful in 17s
Build on push / translation (push) Successful in 16s
Build on push / database (push) Successful in 21s
Build on push / application (push) Successful in 21s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
2025-10-19 20:03:43 +02:00
edraft df2c2b5b56 Workspace check project existence
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 11s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 19s
Build on push / dependency (push) Successful in 19s
Build on push / cli (push) Successful in 19s
Build on push / application (push) Successful in 16s
Build on push / translation (push) Successful in 17s
Build on push / database (push) Successful in 20s
Build on push / mail (push) Successful in 20s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 16s
2025-10-19 19:56:59 +02:00
edraft e3b19c9984 Fixed init command regarding structure
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 19s
Build on push / cli (push) Successful in 16s
Build on push / dependency (push) Successful in 19s
Build on push / application (push) Successful in 20s
Build on push / database (push) Successful in 21s
Build on push / translation (push) Successful in 20s
Build on push / mail (push) Successful in 23s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 14s
2025-10-19 19:48:41 +02:00
edraft 91269f351b Fixed init command regarding package handling
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 11s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 23s
Build on push / cli (push) Successful in 16s
Build on push / dependency (push) Successful in 19s
Build on push / database (push) Successful in 16s
Build on push / application (push) Successful in 18s
Build on push / mail (push) Successful in 20s
Build on push / translation (push) Successful in 20s
Build on push / auth (push) Successful in 19s
Build on push / api (push) Successful in 15s
2025-10-19 19:37:13 +02:00
edraft 5d07973940 Fixed init command regarding package handling
Test before pr merge / test-lint (pull_request) Failing after 7s
Build on push / prepare (push) Successful in 11s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 20s
Build on push / cli (push) Successful in 19s
Build on push / dependency (push) Successful in 22s
Build on push / database (push) Successful in 16s
Build on push / translation (push) Successful in 16s
Build on push / mail (push) Successful in 17s
Build on push / application (push) Successful in 17s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 16s
2025-10-19 19:32:14 +02:00
edraft 0a9b7b81be Fixed init command regarding name None
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 11s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 24s
Build on push / cli (push) Successful in 19s
Build on push / dependency (push) Successful in 18s
Build on push / database (push) Successful in 17s
Build on push / mail (push) Successful in 18s
Build on push / application (push) Successful in 20s
Build on push / translation (push) Successful in 20s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
2025-10-19 19:27:18 +02:00
edraft 21c88bc442 Fixed install.sh after renaming
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 20s
Build on push / dependency (push) Successful in 15s
Build on push / cli (push) Successful in 18s
Build on push / database (push) Successful in 20s
Build on push / translation (push) Successful in 20s
Build on push / application (push) Successful in 21s
Build on push / mail (push) Successful in 24s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 18s
2025-10-19 19:17:14 +02:00
edraft 04e0884e1c Added readme for devs
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 12s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 19s
Build on push / api (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / database (push) Has been cancelled
Build on push / dependency (push) Has been cancelled
Build on push / cli (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / translation (push) Has been cancelled
2025-10-19 19:16:39 +02:00
edraft 2ac7fa4568 Fixed dev build
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 14s
Build on push / core (push) Successful in 21s
Build on push / query (push) Successful in 22s
Build on push / cli (push) Successful in 19s
Build on push / dependency (push) Successful in 21s
Build on push / translation (push) Successful in 16s
Build on push / application (push) Successful in 22s
Build on push / mail (push) Successful in 24s
Build on push / database (push) Successful in 26s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
2025-10-19 14:42:40 +02:00
edraft 43947055e5 Merge pull request 'cli' (#199) from cli into dev
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 13s
Build on push / core (push) Failing after 9s
Build on push / query (push) Failing after 10s
Build on push / api (push) Has been skipped
Build on push / application (push) Has been skipped
Build on push / auth (push) Has been skipped
Build on push / cli (push) Has been skipped
Build on push / database (push) Has been skipped
Build on push / dependency (push) Has been skipped
Build on push / mail (push) Has been skipped
Build on push / translation (push) Has been skipped
Reviewed-on: #199
2025-10-19 14:40:45 +02:00
edraft ba9edfa3fc Added cli dev build
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-10-19 14:40:24 +02:00
edraft 6d3e435da6 cpl new use venv & install deps on creation
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-10-19 14:39:27 +02:00
edraft 76b44ca517 Added cpl new
Test before pr merge / test-lint (pull_request) Successful in 7s
2025-10-18 18:19:39 +02:00
edraft 8ebac05dd8 Cleanup
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-10-16 18:19:36 +02:00
edraft 98ed458d7c Added cpl generate
Test before pr merge / test-lint (pull_request) Successful in 7s
2025-10-16 12:47:06 +02:00
edraft 8d0bc13cc0 Added live dev server
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-10-13 06:22:04 +02:00
edraft 33728cdec3 Added internal extra url
Test before pr merge / test-lint (pull_request) Failing after 6s
2025-10-12 20:45:11 +02:00
edraft f1604f1477 Removed ref for dev
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-10-12 20:22:40 +02:00
edraft a02c101438 Improved spinner
Test before pr merge / test-lint (pull_request) Failing after 8s
2025-10-12 20:02:45 +02:00
edraft 9647923647 Improved build 2025-10-12 18:19:58 +02:00
edraft 883fa2d691 Added build command
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-10-11 18:25:32 +02:00
edraft 3c26c73b41 Added run command
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-10-11 16:31:04 +02:00
edraft faedf328cb Added build command
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-10-11 15:50:38 +02:00
edraft 849dd7a733 [WIP] Started run command 2025-10-11 14:13:28 +02:00
edraft 6e0ae1f25e Cleanup & fixes 2025-10-11 11:43:23 +02:00
edraft 45dcb400da Added remove & add commands
Test before pr merge / test-lint (pull_request) Successful in 7s
2025-10-11 11:05:04 +02:00
edraft 104b736778 Moved commands by context 2025-10-11 09:39:46 +02:00
edraft 90ff8d466d Renamed project dirs
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-10-11 09:32:13 +02:00
edraft f1aaaf2a5b Set version to all packages
Test before pr merge / test-lint (pull_request) Successful in 9s
2025-10-09 21:24:25 +02:00
edraft 5f8519d4b3 Added version
Test before pr merge / test-lint (pull_request) Successful in 7s
2025-10-09 21:08:05 +02:00
edraft c4334f32ed Fixed install & added update 2025-10-09 19:59:02 +02:00
edraft 0a6a17acf6 Added cpl-cli 2025-10-09 17:17:39 +02:00
edraft c02903b009 Merge pull request 'master' (#198) from master into dev
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 11s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 19s
Build on push / dependency (push) Successful in 18s
Build on push / mail (push) Successful in 16s
Build on push / translation (push) Successful in 19s
Build on push / database (push) Successful in 20s
Build on push / application (push) Successful in 29s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 18s
Reviewed-on: #198
2025-10-08 21:31:55 +02:00
edraft b6cf5962aa Merge pull request 'Removed tools & docs' (#197) from dev_cleanup into master
Reviewed-on: #197
2025-10-08 21:30:47 +02:00
edraft d3084041a9 Removed tools & docs 2025-10-08 21:30:01 +02:00
edraft d4af923e4c Merge pull request 'Added gql base #181' (#196) from #181_gql into dev
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 12s
Build on push / query (push) Successful in 23s
Build on push / core (push) Successful in 23s
Build on push / dependency (push) Successful in 29s
Build on push / translation (push) Successful in 17s
Build on push / application (push) Successful in 22s
Build on push / database (push) Successful in 22s
Build on push / mail (push) Successful in 23s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
Reviewed-on: #196
Closes #181
2025-10-08 21:25:41 +02:00
edraft 545540d05d Added subscriptions final #181
Test before pr merge / test-lint (pull_request) Successful in 7s
2025-10-08 21:22:51 +02:00
edraft 3774cef56a Updated permissions #181
Test before pr merge / test-lint (pull_request) Successful in 10s
2025-10-08 17:27:46 +02:00
edraft cdb5e4ff89 [WIP] Subscriptions
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-10-04 06:57:14 +02:00
edraft e362b7fb61 Added/Fixed api_key/user/role gql #181
Test before pr merge / test-lint (pull_request) Failing after 6s
2025-10-04 05:44:13 +02:00
edraft 262e26cb83 Internal api key gql #181 2025-10-04 05:44:13 +02:00
edraft 5b3872a1fe Added possibility to put auth schema to root graphs #181 2025-10-04 05:44:13 +02:00
edraft e7e3712e08 Renamed AuthUsers -> Users & completed user gql #181 2025-10-04 05:44:13 +02:00
edraft df69f1c725 Recursive filter #181 2025-10-04 05:44:13 +02:00
edraft 39351a5eb9 Recursive types #181 2025-10-04 05:44:13 +02:00
edraft 39d06dfe48 Added mutations #181 2025-10-04 05:44:13 +02:00
edraft 3286a95cbf require any #181 2025-10-04 05:44:13 +02:00
edraft 6f46b94998 [WIP] with authentication #181 2025-10-04 05:44:13 +02:00
edraft 20e5da5770 Recursive complex filtering #181 2025-10-04 05:44:13 +02:00
edraft a12a4082db Dao complex filtering #181 2025-10-04 05:44:13 +02:00
edraft d8c60defba Further gql improvements & added test data #181 2025-10-04 05:44:13 +02:00
edraft ada50c693e Changed to strawberry #181 2025-10-04 05:44:13 +02:00
edraft a35b44b3b5 [WIP] collection #181 2025-10-04 05:44:13 +02:00
edraft 683805137a Added arguments to field #181 2025-10-04 05:44:13 +02:00
edraft b0f1fb9839 Removed query base #181 2025-10-04 05:44:13 +02:00
edraft e1ab9cf0db Added gql base #181 2025-10-04 05:44:13 +02:00
edraft 685c20e3bf Added cron jobs as hosted services
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 11s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 14s
Build on push / database (push) Successful in 15s
Build on push / application (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / translation (push) Successful in 29s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 18s
2025-10-04 05:38:21 +02:00
edraft e0f6e1c241 Console & file logging format msg seperate & removed timestamp from console & minor fixes to di
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 14s
Build on push / translation (push) Successful in 16s
Build on push / mail (push) Successful in 17s
Build on push / application (push) Successful in 19s
Build on push / database (push) Successful in 19s
Build on push / auth (push) Successful in 25s
Build on push / api (push) Successful in 14s
2025-09-26 15:48:33 +02:00
edraft c410a692be Better modules
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 17s
Build on push / mail (push) Successful in 16s
Build on push / translation (push) Successful in 16s
Build on push / application (push) Successful in 18s
Build on push / database (push) Successful in 19s
Build on push / auth (push) Successful in 17s
Build on push / api (push) Successful in 14s
2025-09-26 12:55:00 +02:00
edraft 56a16cbeba Module dependencies as static var
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 20s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 16s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 22s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 17s
2025-09-26 08:46:30 +02:00
edraft d05d947d54 Import cleanup
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 12s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / translation (push) Successful in 18s
Build on push / mail (push) Successful in 18s
Build on push / database (push) Successful in 20s
Build on push / auth (push) Successful in 17s
Build on push / api (push) Successful in 13s
2025-09-26 00:03:12 +02:00
edraft 0529269747 Fixed formatting
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 14s
Build on push / mail (push) Successful in 16s
Build on push / application (push) Successful in 19s
Build on push / database (push) Successful in 20s
Build on push / translation (push) Successful in 20s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 15s
2025-09-25 10:37:29 +02:00
edraft e3e1703ff8 Fixed versioning
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 19s
Build on push / dependency (push) Successful in 14s
Build on push / api (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / translation (push) Has been cancelled
Build on push / database (push) Has been cancelled
2025-09-25 10:36:36 +02:00
edraft cf4aa8291f Minor DI fixes & cleanup
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 27s
Build on push / dependency (push) Successful in 18s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 20s
Build on push / translation (push) Successful in 19s
Build on push / mail (push) Successful in 20s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-25 10:29:40 +02:00
edraft 55a727c482 Modularization
Test before pr merge / test-lint (pull_request) Failing after 7s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 16s
Build on push / mail (push) Successful in 15s
Build on push / database (push) Successful in 15s
Build on push / translation (push) Successful in 18s
Build on push / auth (push) Successful in 23s
Build on push / api (push) Successful in 16s
2025-09-25 09:42:07 +02:00
edraft ecb92fca3e Minor fixes 2025-09-25 08:46:02 +02:00
edraft 0ca5e5757a Added hosted services #186
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 8s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 18s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / translation (push) Successful in 17s
Build on push / database (push) Successful in 18s
Build on push / mail (push) Successful in 24s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-25 00:54:09 +02:00
edraft 75417966eb Moved general example 2025-09-25 00:15:26 +02:00
edraft 15d3c59f02 StartupTask #186 2025-09-25 00:11:26 +02:00
edraft 6a3fdb3ebd Fixed formatting #186
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 17s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 16s
Build on push / database (push) Successful in 17s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 18s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-24 21:48:57 +02:00
edraft b49f663ae0 API scoped requests #186
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 21s
Build on push / dependency (push) Successful in 14s
Build on push / api (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / database (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / translation (push) Has been cancelled
2025-09-24 21:47:52 +02:00
edraft 287f5e3149 New implementation of scopes #186 2025-09-24 21:27:28 +02:00
edraft 4c8cd988cc Removed ServiceProviderABC #186 2025-09-24 20:53:01 +02:00
edraft cdb4a0fb34 DI Provider ctx #186 2025-09-24 20:46:43 +02:00
edraft cf8edafd39 Added enumerable order & added array & removed collection
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 20s
Build on push / dependency (push) Successful in 17s
Build on push / translation (push) Successful in 20s
Build on push / application (push) Successful in 21s
Build on push / mail (push) Successful in 24s
Build on push / database (push) Successful in 24s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 14s
2025-09-24 19:41:12 +02:00
edraft 01a2ff7166 Added query@v2 2025-09-24 19:09:11 +02:00
edraft 2da6d679ad Moved test projects 2025-09-24 16:57:24 +02:00
edraft a1cfe76047 Added cache 2025-09-24 12:04:37 +02:00
edraft c71a3df62c More efficient wrapped logger by getting service type not service
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 17s
Build on push / query (push) Successful in 24s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / translation (push) Successful in 15s
Build on push / database (push) Successful in 19s
Build on push / mail (push) Successful in 19s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-24 08:28:50 +02:00
edraft e296c0992b Merge pull request 'Added structured and wrapped logger #187' (#193) from #187_structured_logging into dev
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 17s
Build on push / core (push) Successful in 18s
Build on push / dependency (push) Successful in 18s
Build on push / mail (push) Successful in 15s
Build on push / translation (push) Successful in 15s
Build on push / database (push) Successful in 19s
Build on push / application (push) Successful in 19s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 17s
Reviewed-on: #193
2025-09-23 23:35:55 +02:00
edraft 6639946346 Improved wrapped logging #187
Test before pr merge / test-lint (pull_request) Successful in 6s
2025-09-23 23:34:45 +02:00
edraft b9ac11e15f Added structured and wrapped logger #187
Test before pr merge / test-lint (pull_request) Successful in 5s
2025-09-22 23:24:46 +02:00
edraft 77d821bb6e Added internal imports
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 18s
Build on push / dependency (push) Successful in 14s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 18s
Build on push / translation (push) Successful in 19s
Build on push / mail (push) Successful in 19s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 14s
2025-09-22 22:08:59 +02:00
edraft 86ad953ff1 Authorization via with_route
Test before pr merge / test-lint (pull_request) Successful in 6s
Build on push / prepare (push) Successful in 9s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 18s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 18s
Build on push / database (push) Successful in 19s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-22 22:04:36 +02:00
edraft d6b7eb9b30 Authorization via decorator 2025-09-22 21:16:47 +02:00
edraft 12b7c62b69 Fixed formatting
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 17s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 17s
Build on push / translation (push) Successful in 14s
Build on push / application (push) Successful in 18s
Build on push / database (push) Successful in 17s
Build on push / mail (push) Successful in 18s
Build on push / auth (push) Successful in 13s
Build on push / api (push) Successful in 17s
Test before pr merge / test-lint (pull_request) Successful in 5s
2025-09-21 23:48:09 +02:00
edraft 7fc70747bb Added black test
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 17s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 17s
Build on push / api (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / mail (push) Has started running
Build on push / translation (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / database (push) Has been cancelled
2025-09-21 23:47:15 +02:00
edraft 6de4f3c03a Middleware updated & Fixed mysql pool
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 18s
Build on push / dependency (push) Successful in 17s
Build on push / database (push) Successful in 15s
Build on push / translation (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / application (push) Successful in 21s
Build on push / auth (push) Successful in 14s
Build on push / api (push) Successful in 14s
2025-09-21 23:41:25 +02:00
edraft ea3055527c Changed middleware to asgi 2025-09-21 21:22:19 +02:00
edraft 7b37748ca6 [WIP] validate token via keycloak 2025-09-21 21:07:09 +02:00
edraft 073b35f71a App deps check 2025-09-21 20:11:47 +02:00
edraft eceff6128b [WIP] Authentication
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 16s
Build on push / mail (push) Successful in 16s
Build on push / translation (push) Successful in 18s
Build on push / database (push) Successful in 22s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 13s
2025-09-19 23:01:41 +02:00
edraft 17dfb245bf Minor cleanup 2025-09-19 21:54:08 +02:00
edraft 4f698269b5 Fixed api build
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 17s
Build on push / dependency (push) Successful in 18s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 18s
Build on push / translation (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 14s
2025-09-19 21:12:33 +02:00
574 changed files with 10555 additions and 6459 deletions
+16 -9
View File
@@ -16,61 +16,68 @@ jobs:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, application, auth, core, dependency ]
with:
working_directory: src/cpl-application
working_directory: src/api
secrets: inherit
application:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core, dependency ]
with:
working_directory: src/cpl-application
working_directory: src/application
secrets: inherit
auth:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core, dependency, database ]
with:
working_directory: src/cpl-auth
working_directory: src/auth
secrets: inherit
cli:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core ]
with:
working_directory: src/cli
secrets: inherit
core:
uses: ./.gitea/workflows/package.yaml
needs: [prepare]
with:
working_directory: src/cpl-core
working_directory: src/core
secrets: inherit
database:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core, dependency ]
with:
working_directory: src/cpl-database
working_directory: src/database
secrets: inherit
dependency:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core ]
with:
working_directory: src/cpl-dependency
working_directory: src/dependency
secrets: inherit
mail:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core, dependency ]
with:
working_directory: src/cpl-mail
working_directory: src/mail
secrets: inherit
query:
uses: ./.gitea/workflows/package.yaml
needs: [prepare]
with:
working_directory: src/cpl-query
working_directory: src/query
secrets: inherit
translation:
uses: ./.gitea/workflows/package.yaml
needs: [ prepare, core, dependency ]
with:
working_directory: src/cpl-translation
working_directory: src/translation
secrets: inherit
+6
View File
@@ -36,6 +36,12 @@ jobs:
echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)"
cat pyproject.toml
- name: Set package version
run: |
sed -i -E "s/^__version__ = \".*\"/__version__ = \"$(cat /workspace/sh-edraft.de/cpl/version.txt)\"/" cpl/*/__init__.py
echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)"
cat cpl/*/__init__.py
- name: Set pip conf
run: |
cat > .pip.conf <<'EOF'
+5 -1
View File
@@ -25,7 +25,11 @@ jobs:
git tag
DATE=$(date +'%Y.%m.%d')
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
BUILD_NUMBER=$(($TAG_COUNT + 1))
if [ "$TAG_COUNT" -eq 0 ]; then
BUILD_NUMBER=0
else
BUILD_NUMBER=$(($TAG_COUNT + 1))
fi
VERSION_SUFFIX=${{ inputs.version_suffix }}
if [ -n "$VERSION_SUFFIX" ] && [ "$VERSION_SUFFIX" = "dev" ]; then
+45
View File
@@ -0,0 +1,45 @@
name: Test before pr merge
run-name: Test before pr merge
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
jobs:
test-lint:
runs-on: [ runner ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Installing black
run: python3.12 -m pip install black
- name: Checking black
run: python3.12 -m black src --check
test:
runs-on: [ runner ]
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
steps:
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
with:
token: ${{ secrets.CI_ACCESS_TOKEN }}
- name: Setting up Python 3.12
run: |
apt install python3-dev -y
bash ./install.sh
bash ./install.sh -dev
- name: Testing with pytest
run: |
python3.12 -m pytest
+3
View File
@@ -139,3 +139,6 @@ PythonImportHelper-v2-Completion.json
# cpl unittest stuff
unittests/test_*_playground
# cpl logs
**/logs/*.jsonl
+22
View File
@@ -0,0 +1,22 @@
## Prepare for development
After cloning the repository, run the following commands to set up your development environment:
```bash
python -m venv .venv
source .venv/bin/activate
# On Windows use `.venv\Scripts\activate`
# On Windows with git bash use `source .venv/Scripts/activate`
bash install.sh
```
Install cpl-cli as a development package:
```bash
pip install -e src/core
pip install -e src/cli
# test with:
cpl v
```
When using Pycharm, mark all directories under `src/` as "Sources Root" and `exa` to ensure proper module resolution.
+13
View File
@@ -0,0 +1,13 @@
{
"name": "cpl",
"projects": [
"src/cli/cpl.project.json",
"src/core/cpl.project.json",
"src/mail/cpl.project.json",
"test/cpl.project.json"
],
"defaultProject": "cpl-cli",
"scripts": {
"format": "black src"
}
}
+147
View File
@@ -0,0 +1,147 @@
from starlette.responses import JSONResponse
from cpl.dependency.event_bus import EventBusABC
from cpl.graphql.event_bus.memory import InMemoryEventBus
from queries.cities import CityGraphType, CityFilter, CitySort
from queries.hello import UserGraphType # , UserFilter, UserSort, UserGraphType
from queries.user import UserFilter, UserSort
from cpl.api.api_module import ApiModule
from cpl.application.application_builder import ApplicationBuilder
from cpl.auth.schema import User, Role
from cpl.core.configuration import Configuration
from cpl.core.console import Console
from cpl.core.environment import Environment
from cpl.core.utils.cache import Cache
from cpl.database.mysql.mysql_module import MySQLModule
from cpl.graphql.application.graphql_app import GraphQLApp
from cpl.graphql.auth.graphql_auth_module import GraphQLAuthModule
from cpl.graphql.graphql_module import GraphQLModule
from model.author_dao import AuthorDao
from model.author_query import AuthorGraphType, AuthorFilter, AuthorSort
from model.post_dao import PostDao
from model.post_query import PostFilter, PostSort, PostGraphType, PostMutation, PostSubscription
from permissions import PostPermissions
from queries.hello import HelloQuery
from scoped_service import ScopedService
from service import PingService
from test_data_seeder import TestDataSeeder
def main():
builder = ApplicationBuilder[GraphQLApp](GraphQLApp)
Configuration.add_json_file(f"appsettings.json")
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
# builder.services.add_logging()
(
builder.services.add_structured_logging()
.add_transient(PingService)
.add_module(MySQLModule)
.add_module(ApiModule)
.add_module(GraphQLModule)
.add_module(GraphQLAuthModule)
.add_scoped(ScopedService)
.add_singleton(EventBusABC, InMemoryEventBus)
.add_cache(User)
.add_cache(Role)
.add_transient(CityGraphType)
.add_transient(CityFilter)
.add_transient(CitySort)
.add_transient(UserGraphType)
.add_transient(UserFilter)
.add_transient(UserSort)
# .add_transient(UserGraphType)
# .add_transient(UserFilter)
# .add_transient(UserSort)
.add_transient(HelloQuery)
# test data
.add_singleton(TestDataSeeder)
# authors
.add_transient(AuthorDao)
.add_transient(AuthorGraphType)
.add_transient(AuthorFilter)
.add_transient(AuthorSort)
# posts
.add_transient(PostDao)
.add_transient(PostGraphType)
.add_transient(PostFilter)
.add_transient(PostSort)
.add_transient(PostMutation)
.add_transient(PostSubscription)
)
app = builder.build()
app.with_logging()
app.with_migrations("./scripts")
app.with_authentication()
app.with_authorization()
app.with_route(
path="/route1",
fn=lambda r: JSONResponse("route1"),
method="GET",
# authentication=True,
# permissions=[Permissions.administrator],
)
app.with_routes_directory("routes")
schema = app.with_graphql()
schema.query.string_field("ping", resolver=lambda: "pong")
schema.query.with_query("hello", HelloQuery)
schema.query.dao_collection_field(AuthorGraphType, AuthorDao, "authors", AuthorFilter, AuthorSort)
(
schema.query.dao_collection_field(PostGraphType, PostDao, "posts", PostFilter, PostSort)
# .with_require_any_permission(PostPermissions.read)
.with_public()
)
schema.mutation.with_mutation("post", PostMutation).with_public()
schema.subscription.with_subscription(PostSubscription)
app.with_auth_root_queries(True)
app.with_auth_root_mutations(True)
app.with_playground()
app.with_graphiql()
app.with_permissions(PostPermissions)
provider = builder.service_provider
user_cache = provider.get_service(Cache[User])
role_cache = provider.get_service(Cache[Role])
if role_cache == user_cache:
raise Exception("Cache service is not working")
s1 = provider.get_service(ScopedService)
s2 = provider.get_service(ScopedService)
if s1.name == s2.name:
raise Exception("Scoped service is not working")
with provider.create_scope() as scope:
s3 = scope.get_service(ScopedService)
s4 = scope.get_service(ScopedService)
if s3.name != s4.name:
raise Exception("Scoped service is not working")
if s1.name == s3.name:
raise Exception("Scoped service is not working")
Console.write_line(
s1.name,
s2.name,
s3.name,
s4.name,
)
app.run()
if __name__ == "__main__":
main()
+30
View File
@@ -0,0 +1,30 @@
from datetime import datetime
from typing import Self
from cpl.core.typing import SerialId
from cpl.database.abc import DbModelABC
class Author(DbModelABC[Self]):
def __init__(
self,
id: int,
first_name: str,
last_name: str,
deleted: bool = False,
editor_id: SerialId | None = None,
created: datetime | None = None,
updated: datetime | None = None,
):
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
self._first_name = first_name
self._last_name = last_name
@property
def first_name(self) -> str:
return self._first_name
@property
def last_name(self) -> str:
return self._last_name
+11
View File
@@ -0,0 +1,11 @@
from cpl.database.abc import DbModelDaoABC
from model.author import Author
class AuthorDao(DbModelDaoABC):
def __init__(self):
DbModelDaoABC.__init__(self, Author, "authors")
self.attribute(Author.first_name, str, db_name="firstname")
self.attribute(Author.last_name, str, db_name="lastname")
+37
View File
@@ -0,0 +1,37 @@
from cpl.graphql.schema.db_model_graph_type import DbModelGraphType
from cpl.graphql.schema.filter.db_model_filter import DbModelFilter
from cpl.graphql.schema.sort.sort import Sort
from cpl.graphql.schema.sort.sort_order import SortOrder
from model.author import Author
class AuthorFilter(DbModelFilter[Author]):
def __init__(self):
DbModelFilter.__init__(self, public=True)
self.int_field("id")
self.string_field("firstName")
self.string_field("lastName")
class AuthorSort(Sort[Author]):
def __init__(self):
Sort.__init__(self)
self.field("id", SortOrder)
self.field("firstName", SortOrder)
self.field("lastName", SortOrder)
class AuthorGraphType(DbModelGraphType[Author]):
def __init__(self):
DbModelGraphType.__init__(self, public=True)
self.int_field(
"id",
resolver=lambda root: root.id,
).with_public(True)
self.string_field(
"firstName",
resolver=lambda root: root.first_name,
).with_public(True)
self.string_field(
"lastName",
resolver=lambda root: root.last_name,
).with_public(True)
+44
View File
@@ -0,0 +1,44 @@
from datetime import datetime
from typing import Self
from cpl.core.typing import SerialId
from cpl.database.abc import DbModelABC
class Post(DbModelABC[Self]):
def __init__(
self,
id: int,
author_id: SerialId,
title: str,
content: str,
deleted: bool = False,
editor_id: SerialId | None = None,
created: datetime | None = None,
updated: datetime | None = None,
):
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
self._author_id = author_id
self._title = title
self._content = content
@property
def author_id(self) -> SerialId:
return self._author_id
@property
def title(self) -> str:
return self._title
@title.setter
def title(self, value: str):
self._title = value
@property
def content(self) -> str:
return self._content
@content.setter
def content(self, value: str):
self._content = value
+15
View File
@@ -0,0 +1,15 @@
from cpl.database.abc import DbModelDaoABC
from model.author_dao import AuthorDao
from model.post import Post
class PostDao(DbModelDaoABC[Post]):
def __init__(self, authors: AuthorDao):
DbModelDaoABC.__init__(self, Post, "posts")
self.attribute(Post.author_id, int, db_name="authorId")
self.reference("author", "id", Post.author_id, "authors", authors)
self.attribute(Post.title, str)
self.attribute(Post.content, str)
+148
View File
@@ -0,0 +1,148 @@
from cpl.dependency.event_bus import EventBusABC
from cpl.graphql.query_context import QueryContext
from cpl.graphql.schema.db_model_graph_type import DbModelGraphType
from cpl.graphql.schema.filter.db_model_filter import DbModelFilter
from cpl.graphql.schema.input import Input
from cpl.graphql.schema.mutation import Mutation
from cpl.graphql.schema.sort.sort import Sort
from cpl.graphql.schema.sort.sort_order import SortOrder
from cpl.graphql.schema.subscription import Subscription
from model.author_dao import AuthorDao
from model.author_query import AuthorGraphType, AuthorFilter
from model.post import Post
from model.post_dao import PostDao
class PostFilter(DbModelFilter[Post]):
def __init__(self):
DbModelFilter.__init__(self, public=True)
self.int_field("id")
self.filter_field("author", AuthorFilter)
self.string_field("title")
self.string_field("content")
class PostSort(Sort[Post]):
def __init__(self):
Sort.__init__(self)
self.field("id", SortOrder)
self.field("title", SortOrder)
self.field("content", SortOrder)
class PostGraphType(DbModelGraphType[Post]):
def __init__(self, authors: AuthorDao):
DbModelGraphType.__init__(self, public=True)
self.int_field(
"id",
resolver=lambda root: root.id,
).with_optional().with_public(True)
async def _a(root: Post):
return await authors.get_by_id(root.author_id)
def r_name(ctx: QueryContext):
return ctx.user.username == "admin"
self.object_field("author", AuthorGraphType, resolver=_a).with_public(True) # .with_require_any([], [r_name]))
self.string_field(
"title",
resolver=lambda root: root.title,
).with_public(True)
self.string_field(
"content",
resolver=lambda root: root.content,
).with_public(True)
class PostCreateInput(Input[Post]):
title: str
content: str
author_id: int
def __init__(self):
Input.__init__(self)
self.string_field("title").with_required()
self.string_field("content").with_required()
self.int_field("author_id").with_required()
class PostUpdateInput(Input[Post]):
title: str
content: str
author_id: int
def __init__(self):
Input.__init__(self)
self.int_field("id").with_required()
self.string_field("title").with_required(False)
self.string_field("content").with_required(False)
class PostSubscription(Subscription):
def __init__(self, bus: EventBusABC):
Subscription.__init__(self)
self._bus = bus
def selector(event: Post, info) -> bool:
return event.id == 101
self.subscription_field("postChange", PostGraphType, selector).with_public()
class PostMutation(Mutation):
def __init__(self, posts: PostDao, authors: AuthorDao, bus: EventBusABC):
Mutation.__init__(self)
self._posts = posts
self._authors = authors
self._bus = bus
self.field("create", int, resolver=self.create_post).with_public().with_required().with_argument(
"input",
PostCreateInput,
).with_required()
self.field("update", bool, resolver=self.update_post).with_public().with_required().with_argument(
"input",
PostUpdateInput,
).with_required()
self.field("delete", bool, resolver=self.delete_post).with_public().with_required().with_argument(
"id",
int,
).with_required()
self.field("restore", bool, resolver=self.restore_post).with_public().with_required().with_argument(
"id",
int,
).with_required()
async def create_post(self, input: PostCreateInput) -> int:
return await self._posts.create(Post(0, input.author_id, input.title, input.content))
async def update_post(self, input: PostUpdateInput) -> bool:
post = await self._posts.get_by_id(input.id)
if post is None:
return False
post.title = input.title if input.title is not None else post.title
post.content = input.content if input.content is not None else post.content
await self._posts.update(post)
await self._bus.publish("postChange", post)
return True
async def delete_post(self, id: int) -> bool:
post = await self._posts.get_by_id(id)
if post is None:
return False
await self._posts.delete(post)
return True
async def restore_post(self, id: int) -> bool:
post = await self._posts.get_by_id(id)
if post is None:
return False
await self._posts.restore(post)
return True
+8
View File
@@ -0,0 +1,8 @@
from enum import Enum
class PostPermissions(Enum):
read = "post.read"
write = "post.write"
delete = "post.delete"
+39
View File
@@ -0,0 +1,39 @@
from cpl.graphql.schema.filter.filter import Filter
from cpl.graphql.schema.graph_type import GraphType
from cpl.graphql.schema.sort.sort import Sort
from cpl.graphql.schema.sort.sort_order import SortOrder
class City:
def __init__(self, id: int, name: str):
self.id = id
self.name = name
class CityFilter(Filter[City]):
def __init__(self):
Filter.__init__(self)
self.field("id", int)
self.field("name", str)
class CitySort(Sort[City]):
def __init__(self):
Sort.__init__(self)
self.field("id", SortOrder)
self.field("name", SortOrder)
class CityGraphType(GraphType[City]):
def __init__(self):
GraphType.__init__(self)
self.int_field(
"id",
resolver=lambda root: root.id,
)
self.string_field(
"name",
resolver=lambda root: root.name,
)
+70
View File
@@ -0,0 +1,70 @@
from queries.cities import CityFilter, CitySort, CityGraphType, City
from queries.user import User, UserFilter, UserSort, UserGraphType
from cpl.api.middleware.request import get_request
from cpl.auth.schema import UserDao, User
from cpl.graphql.schema.filter.filter import Filter
from cpl.graphql.schema.graph_type import GraphType
from cpl.graphql.schema.query import Query
from cpl.graphql.schema.sort.sort import Sort
from cpl.graphql.schema.sort.sort_order import SortOrder
users = [User(i, f"User {i}") for i in range(1, 101)]
cities = [City(i, f"City {i}") for i in range(1, 101)]
# class UserFilter(Filter[User]):
# def __init__(self):
# Filter.__init__(self)
# self.field("id", int)
# self.field("username", str)
#
#
# class UserSort(Sort[User]):
# def __init__(self):
# Sort.__init__(self)
# self.field("id", SortOrder)
# self.field("username", SortOrder)
#
# class UserGraphType(GraphType[User]):
#
# def __init__(self):
# GraphType.__init__(self)
#
# self.int_field(
# "id",
# resolver=lambda root: root.id,
# )
# self.string_field(
# "username",
# resolver=lambda root: root.username,
# )
class HelloQuery(Query):
def __init__(self):
Query.__init__(self)
self.string_field(
"message",
resolver=lambda name: f"Hello {name} {get_request().state.request_id}",
).with_argument("name", str, "Name to greet", "world")
self.collection_field(
UserGraphType,
"users",
UserFilter,
UserSort,
resolver=lambda: users,
)
self.collection_field(
CityGraphType,
"cities",
CityFilter,
CitySort,
resolver=lambda: cities,
)
# self.dao_collection_field(
# UserGraphType,
# UserDao,
# "Users",
# UserFilter,
# UserSort,
# )
+39
View File
@@ -0,0 +1,39 @@
from cpl.graphql.schema.filter.filter import Filter
from cpl.graphql.schema.graph_type import GraphType
from cpl.graphql.schema.sort.sort import Sort
from cpl.graphql.schema.sort.sort_order import SortOrder
class User:
def __init__(self, id: int, name: str):
self.id = id
self.name = name
class UserFilter(Filter[User]):
def __init__(self):
Filter.__init__(self)
self.field("id", int)
self.field("name", str)
class UserSort(Sort[User]):
def __init__(self):
Sort.__init__(self)
self.field("id", SortOrder)
self.field("name", SortOrder)
class UserGraphType(GraphType[User]):
def __init__(self):
GraphType.__init__(self)
self.int_field(
"id",
resolver=lambda root: root.id,
)
self.string_field(
"name",
resolver=lambda root: root.name,
)
+21
View File
@@ -0,0 +1,21 @@
from urllib.request import Request
from service import PingService
from starlette.responses import JSONResponse
from cpl.api import APILogger
from cpl.api.router import Router
from cpl.core.console import Console
from cpl.dependency import ServiceProvider
from scoped_service import ScopedService
@Router.authenticate()
# @Router.authorize(permissions=[Permissions.administrator])
# @Router.authorize(policies=["test"])
@Router.get(f"/ping")
async def ping(r: Request, ping: PingService, logger: APILogger, provider: ServiceProvider, scoped: ScopedService):
logger.info(f"Ping: {ping}")
Console.write_line(scoped.name)
return JSONResponse(ping.ping(r))
+14
View File
@@ -0,0 +1,14 @@
from cpl.core.console.console import Console
from cpl.core.utils.string import String
class ScopedService:
def __init__(self):
self._name = String.random(8)
@property
def name(self) -> str:
return self._name
def run(self):
Console.write_line(f"Im {self._name}")
+22
View File
@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS `authors` (
`id` INT(30) NOT NULL AUTO_INCREMENT,
`firstname` VARCHAR(64) NOT NULL,
`lastname` VARCHAR(64) NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
editorId INT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`id`)
);
CREATE TABLE IF NOT EXISTS `posts` (
`id` INT(30) NOT NULL AUTO_INCREMENT,
`authorId` INT(30) NOT NULL REFERENCES `authors`(`id`) ON DELETE CASCADE,
`title` TEXT NOT NULL,
`content` TEXT NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
editorId INT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`id`)
);
+48
View File
@@ -0,0 +1,48 @@
from faker import Faker
from cpl.database.abc import DataSeederABC
from cpl.query import Enumerable
from model.author import Author
from model.author_dao import AuthorDao
from model.post import Post
from model.post_dao import PostDao
fake = Faker()
class TestDataSeeder(DataSeederABC):
def __init__(self, authors: AuthorDao, posts: PostDao):
DataSeederABC.__init__(self)
self._authors = authors
self._posts = posts
async def seed(self):
if await self._authors.count() == 0:
await self._seed_authors()
if await self._posts.count() == 0:
await self._seed_posts()
async def _seed_authors(self):
authors = Enumerable.range(0, 35).select(
lambda x: Author(
0,
fake.first_name(),
fake.last_name(),
)
).to_list()
await self._authors.create_many(authors, skip_editor=True)
async def _seed_posts(self):
posts = Enumerable.range(0, 100).select(
lambda x: Post(
id=0,
author_id=fake.random_int(min=1, max=35),
title=fake.sentence(nb_words=6),
content=fake.paragraph(nb_sentences=6),
)
).to_list()
await self._posts.create_many(posts, skip_editor=True)
@@ -3,7 +3,8 @@ from cpl.auth.keycloak import KeycloakAdmin
from cpl.core.console import Console
from cpl.core.environment import Environment
from cpl.core.log import LoggerABC
from cpl.dependency import ServiceProviderABC
from cpl.dependency import ServiceProvider
from cpl.dependency.typing import Modules
from model.city import City
from model.city_dao import CityDao
from model.user import User
@@ -11,8 +12,8 @@ from model.user_dao import UserDao
class Application(ApplicationABC):
def __init__(self, services: ServiceProviderABC):
ApplicationABC.__init__(self, services)
def __init__(self, services: ServiceProvider, modules: Modules):
ApplicationABC.__init__(self, services, modules)
self._logger = services.get_service(LoggerABC)
@@ -5,21 +5,22 @@
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"Logging": {
"Path": "logs/$date_now/",
"Filename": "bot.log",
"Log": {
"Path": "logs/",
"Filename": "log_$start_time.log",
"ConsoleLevel": "TRACE",
"Level": "TRACE"
},
"Database": {
"Host": "localhost",
"User": "local",
"Password": "bG9jYWw=",
"Database": "local",
"Port": "3306",
"User": "cpl",
"Port": 3306,
"Password": "cpl",
"Database": "cpl",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
"Buffered": "true"
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"TimeFormat": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"Log": {
"Path": "logs/",
"Filename": "log_$start_time.log",
"ConsoleLevel": "ERROR",
"Level": "WARNING"
}
}
@@ -3,6 +3,7 @@ from cpl.application import ApplicationBuilder
from cpl.auth.permission.permissions_registry import PermissionsRegistry
from cpl.core.console import Console
from cpl.core.log import LogLevel
from cpl.database import DatabaseModule
from custom_permissions import CustomPermissions
from startup import Startup
@@ -10,13 +11,12 @@ from startup import Startup
def main():
builder = ApplicationBuilder(Application).with_startup(Startup)
builder.services.add_logging()
app = builder.build()
app.with_logging(LogLevel.trace)
app.with_permissions(CustomPermissions)
app.with_migrations("./scripts")
app.with_seeders()
# app.with_seeders()
Console.write_line(CustomPermissions.test.value in PermissionsRegistry.get())
app.run()
@@ -5,16 +5,16 @@ from cpl.core.typing import SerialId
from cpl.database.abc.db_model_abc import DbModelABC
class City(DbModelABC):
class City(DbModelABC[Self]):
def __init__(
self,
id: int,
name: str,
zip: str,
deleted: bool = False,
editor_id: Optional[SerialId] = None,
created: Optional[datetime] = None,
updated: Optional[datetime] = None,
editor_id: SerialId | None = None,
created: datetime | None= None,
updated: datetime | None= None,
):
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
self._name = name
@@ -5,7 +5,7 @@ from model.city import City
class CityDao(DbModelDaoABC[City]):
def __init__(self):
DbModelDaoABC.__init__(self, __name__, City, "city")
DbModelDaoABC.__init__(self, City, "city")
self.attribute(City.name, str)
self.attribute(City.zip, int)
@@ -5,7 +5,7 @@ from cpl.core.typing import SerialId
from cpl.database.abc.db_model_abc import DbModelABC
class User(DbModelABC):
class User(DbModelABC[Self]):
def __init__(
self,
@@ -13,9 +13,9 @@ class User(DbModelABC):
name: str,
city_id: int = 0,
deleted: bool = False,
editor_id: Optional[SerialId] = None,
created: Optional[datetime] = None,
updated: Optional[datetime] = None,
editor_id: SerialId | None = None,
created: datetime | None= None,
updated: datetime | None= None,
):
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
self._name = name
@@ -5,7 +5,7 @@ from model.user import User
class UserDao(DbModelDaoABC[User]):
def __init__(self):
DbModelDaoABC.__init__(self, __name__, User, "users")
DbModelDaoABC.__init__(self, User, "users")
self.attribute(User.name, str)
self.attribute(User.city_id, int, db_name="CityId")
@@ -1,11 +1,14 @@
from cpl import auth
from cpl.application.abc.startup_abc import StartupABC
from cpl.auth import permission
from cpl.auth.auth_module import AuthModule
from cpl.auth.permission.permission_module import PermissionsModule
from cpl.core.configuration import Configuration
from cpl.core.environment import Environment
from cpl.core.log import Logger, LoggerABC
from cpl.database import mysql
from cpl.database import mysql, DatabaseModule
from cpl.database.abc.data_access_object_abc import DataAccessObjectABC
from cpl.database.mysql.mysql_module import MySQLModule
from cpl.dependency import ServiceCollection
from model.city_dao import CityDao
from model.user_dao import UserDao
@@ -21,9 +24,10 @@ class Startup(StartupABC):
@staticmethod
async def configure_services(services: ServiceCollection):
services.add_module(mysql)
services.add_module(auth)
services.add_module(permission)
services.add_module(MySQLModule)
services.add_module(DatabaseModule)
services.add_module(AuthModule)
services.add_module(PermissionsModule)
services.add_transient(DataAccessObjectABC, UserDao)
services.add_transient(DataAccessObjectABC, CityDao)
+45
View File
@@ -0,0 +1,45 @@
from cpl.application.abc import ApplicationABC
from cpl.core.console.console import Console
from cpl.dependency import ServiceProvider
from test_abc import TestABC
from test_service import TestService
from di_tester_service import DITesterService
from tester import Tester
class Application(ApplicationABC):
def __init__(self, services: ServiceProvider):
ApplicationABC.__init__(self, services)
def _part_of_scoped(self):
ts: TestService = self._services.get_service(TestService)
ts.run()
def main(self):
with self._services.create_scope() as scope:
Console.write_line("Scope1")
ts: TestService = scope.get_service(TestService)
ts.run()
dit: DITesterService = scope.get_service(DITesterService)
dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
with self._services.create_scope() as scope:
Console.write_line("Scope2")
ts: TestService = scope.get_service(TestService)
ts.run()
dit: DITesterService = scope.get_service(DITesterService)
dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
Console.write_line("Global")
self._part_of_scoped()
#from static_test import StaticTest
#StaticTest.test()
self._services.get_service(Tester)
Console.write_line(self._services.get_services(TestABC))
@@ -1,11 +1,15 @@
from cpl.core.console.console import Console
from di.test_service import TestService
from test_service import TestService
class DITesterService:
def __init__(self, ts: TestService):
self._ts = ts
@property
def name(self) -> str:
return self._ts.name
def run(self):
Console.write_line("DIT: ")
self._ts.run()
@@ -1,7 +1,7 @@
from cpl.application import ApplicationBuilder
from di.application import Application
from di.startup import Startup
from application import Application
from startup import Startup
def main():
+27
View File
@@ -0,0 +1,27 @@
from cpl.application.abc import StartupABC
from cpl.dependency import ServiceProvider, ServiceCollection
from di_tester_service import DITesterService
from test1_service import Test1Service
from test2_service import Test2Service
from test_abc import TestABC
from test_service import TestService
from tester import Tester
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
@staticmethod
def configure_configuration(): ...
@staticmethod
def configure_services(services: ServiceCollection) -> ServiceProvider:
services.add_scoped(TestService)
services.add_scoped(DITesterService)
services.add_singleton(TestABC, Test1Service)
services.add_singleton(TestABC, Test2Service)
services.add_singleton(Tester)
return services.build()
+10
View File
@@ -0,0 +1,10 @@
from cpl.dependency import ServiceProvider, ServiceProvider
from cpl.dependency.inject import inject
from test_service import TestService
class StaticTest:
@staticmethod
@inject
def test(services: ServiceProvider, t1: TestService):
t1.run()
@@ -1,12 +1,12 @@
import string
from cpl.core.console.console import Console
from cpl.core.utils.string import String
from di.test_abc import TestABC
from test_abc import TestABC
class Test1Service(TestABC):
def __init__(self):
TestABC.__init__(self, String.random_string(string.ascii_lowercase, 8))
TestABC.__init__(self, String.random(8))
def run(self):
Console.write_line(f"Im {self._name}")
@@ -1,12 +1,12 @@
import string
from cpl.core.console.console import Console
from cpl.core.utils.string import String
from di.test_abc import TestABC
from test_abc import TestABC
class Test2Service(TestABC):
def __init__(self):
TestABC.__init__(self, String.random_string(string.ascii_lowercase, 8))
TestABC.__init__(self, String.random(8))
def run(self):
Console.write_line(f"Im {self._name}")
@@ -1,5 +1,3 @@
import string
from cpl.core.console.console import Console
from cpl.core.utils.string import String
@@ -8,5 +6,9 @@ class TestService:
def __init__(self):
self._name = String.random(8)
@property
def name(self) -> str:
return self._name
def run(self):
Console.write_line(f"Im {self._name}")
+7
View File
@@ -0,0 +1,7 @@
from cpl.core.console.console import Console
from test_abc import TestABC
class Tester:
def __init__(self, t1: TestABC, t2: TestABC, t3: TestABC, t: list[TestABC]):
Console.write_line("Tester:", t, t1, t2, t3)
@@ -1,23 +1,25 @@
import asyncio
import time
from typing import Optional
from cpl.application.abc import ApplicationABC
from cpl.core.configuration import Configuration
from cpl.core.console import Console
from cpl.dependency import ServiceProviderABC
from cpl.core.environment import Environment
from cpl.core.log import LoggerABC
from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceProvider
from cpl.dependency.typing import Modules
from cpl.mail import EMail, EMailClientABC
from cpl.query.extension.list import List
from cpl.query import List
from scoped_service import ScopedService
from test_service import TestService
from test_settings import TestSettings
class Application(ApplicationABC):
def __init__(self, services: ServiceProviderABC):
ApplicationABC.__init__(self, services)
def __init__(self, services: ServiceProvider, modules: Modules):
ApplicationABC.__init__(self, services, modules)
self._logger = self._services.get_service(LoggerABC)
self._mailer = self._services.get_service(EMailClientABC)
@@ -35,10 +37,10 @@ class Application(ApplicationABC):
def _wait(time_ms: int):
time.sleep(time_ms)
def main(self):
async def main(self):
self._logger.debug(f"Host: {Environment.get_host_name()}")
self._logger.debug(f"Environment: {Environment.get_environment()}")
Console.write_line(List(int, range(0, 10)).select(lambda x: f"x={x}").to_list())
Console.write_line(List(range(0, 10)).select(lambda x: f"x={x}").to_list())
Console.spinner("Test", self._wait, 2, spinner_foreground_color="red")
test: TestService = self._services.get_service(TestService)
ip_pipe: IPAddressPipe = self._services.get_service(IPAddressPipe)
@@ -48,10 +50,21 @@ class Application(ApplicationABC):
Console.write_line(f"DI working: {test == test2 and ip_pipe != ip_pipe2}")
Console.write_line(self._services.get_service(LoggerABC))
scope = self._services.create_scope()
Console.write_line("scope", scope)
with self._services.create_scope() as s:
Console.write_line("with scope", s)
root_scoped_service = self._services.get_service(ScopedService)
with self._services.create_scope() as scope:
s_srvc1 = scope.get_service(ScopedService)
s_srvc2 = scope.get_service(ScopedService)
Console.write_line(root_scoped_service)
Console.write_line(s_srvc1)
Console.write_line(s_srvc2)
if root_scoped_service == s_srvc1 or s_srvc1 != s_srvc2:
raise Exception("Root scoped service should not be equal to scoped service")
root_scoped_service2 = self._services.get_service(ScopedService)
Console.write_line(root_scoped_service2)
if root_scoped_service == root_scoped_service2:
raise Exception("Root scoped service should be equal to root scoped service 2")
test_settings = Configuration.get(TestSettings)
Console.write_line(test_settings.value)
@@ -0,0 +1,8 @@
{
"Logging": {
"Path": "logs/",
"Filename": "log_$start_time.log",
"ConsoleLevel": "TRACE",
"Level": "TRACE"
}
}
+30
View File
@@ -0,0 +1,30 @@
import asyncio
from datetime import datetime
from cpl.core.console import Console
from cpl.core.time.cron import Cron
from cpl.core.service.cronjob import CronjobABC
from cpl.core.service.hosted_service import HostedService
class Hosted(HostedService):
def __init__(self):
self._stopped = False
async def start(self):
Console.write_line("Hosted Service Started")
while not self._stopped:
Console.write_line("Hosted Service Running")
await asyncio.sleep(5)
async def stop(self):
Console.write_line("Hosted Service Stopped")
self._stopped = True
class MyCronJob(CronjobABC):
def __init__(self):
CronjobABC.__init__(self, Cron("*/1 * * * *")) # Every minute
async def loop(self):
Console.write_line(f"[{datetime.now()}] Hello from Cronjob!")
@@ -1,11 +1,13 @@
from application import Application
from cpl.application import ApplicationBuilder
from cpl.core.console import Console
from test_extension import TestExtension
from startup import Startup
from test_startup_extension import TestStartupExtension
def main():
Console.write_line("\n\n--- Application Starting ---\n")
app_builder = ApplicationBuilder(Application)
app_builder.with_startup(Startup)
app_builder.with_extension(TestStartupExtension)
+10
View File
@@ -0,0 +1,10 @@
from cpl.core.console import Console
class ScopedService:
def __init__(self):
self.value = "I am a scoped service"
Console.write_line(self.value, self)
def get_value(self):
return self.value
@@ -1,9 +1,11 @@
from cpl import mail
from cpl.application.abc import StartupABC
from cpl.core.configuration import Configuration
from cpl.core.environment import Environment
from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceCollection
from cpl.mail.mail_module import MailModule
from hosted_service import Hosted, MyCronJob
from scoped_service import ScopedService
from test_service import TestService
@@ -18,6 +20,9 @@ class Startup(StartupABC):
@staticmethod
def configure_services(services: ServiceCollection):
services.add_logging()
services.add_module(mail)
services.add_module(MailModule)
services.add_transient(IPAddressPipe)
services.add_singleton(TestService)
services.add_scoped(ScopedService)
services.add_hosted_service(Hosted)
services.add_hosted_service(MyCronJob)
@@ -1,10 +1,10 @@
from cpl.application.abc import ApplicationExtensionABC
from cpl.core.console import Console
from cpl.dependency import ServiceProviderABC
from cpl.dependency import ServiceProvider
class TestExtension(ApplicationExtensionABC):
@staticmethod
def run(services: ServiceProviderABC):
def run(services: ServiceProvider):
Console.write_line("Hello World from App Extension")
@@ -1,10 +1,10 @@
from cpl.core.console.console import Console
from cpl.dependency import ServiceProviderABC
from cpl.dependency import ServiceProvider
from cpl.core.pipes.ip_address_pipe import IPAddressPipe
class TestService:
def __init__(self, provider: ServiceProviderABC):
def __init__(self, provider: ServiceProvider):
self._provider = provider
def run(self):
+60
View File
@@ -0,0 +1,60 @@
from cpl.core.console import Console
from cpl.core.utils.benchmark import Benchmark
from cpl.query.enumerable import Enumerable
from cpl.query.immutable_list import ImmutableList
from cpl.query.list import List
from cpl.query.set import Set
def _default():
Console.write_line(Enumerable.empty().to_list())
Console.write_line(Enumerable.range(0, 100).length)
Console.write_line(Enumerable.range(0, 100).to_list())
Console.write_line(Enumerable.range(0, 100).where(lambda x: x % 2 == 0).length)
Console.write_line(
Enumerable.range(0, 100).where(lambda x: x % 2 == 0).to_list().select(lambda x: str(x)).to_list()
)
Console.write_line(List)
s =Enumerable.range(0, 10).to_set()
Console.write_line(s)
s.add(1)
Console.write_line(s)
data = Enumerable(
[
{"name": "Alice", "age": 30},
{"name": "Dave", "age": 35},
{"name": "Charlie", "age": 25},
{"name": "Bob", "age": 25},
]
)
Console.write_line(data.order_by(lambda x: x["age"]).to_list())
Console.write_line(data.order_by(lambda x: x["age"]).then_by(lambda x: x["name"]).to_list())
Console.write_line(data.order_by(lambda x: x["name"]).then_by(lambda x: x["age"]).to_list())
def t_benchmark(data: list):
Benchmark.all("Enumerable", lambda: Enumerable(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
Benchmark.all("Set", lambda: Set(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
Benchmark.all("List", lambda: List(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
Benchmark.all(
"ImmutableList", lambda: ImmutableList(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list()
)
Benchmark.all("List comprehension", lambda: [x * 2 for x in data if x % 2 == 0])
def main():
N = 1_000_000
data = list(range(N))
t_benchmark(data)
Console.write_line()
_default()
if __name__ == "__main__":
main()
@@ -1,14 +1,14 @@
from cpl.application import ApplicationABC
from cpl.core.configuration import ConfigurationABC
from cpl.core.console import Console
from cpl.dependency import ServiceProviderABC
from cpl.dependency import ServiceProvider
from cpl.translation.translate_pipe import TranslatePipe
from cpl.translation.translation_service_abc import TranslationServiceABC
from cpl.translation.translation_settings import TranslationSettings
class Application(ApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
def __init__(self, config: ConfigurationABC, services: ServiceProvider):
ApplicationABC.__init__(self, config, services)
self._translate: TranslatePipe = services.get_service(TranslatePipe)
@@ -1,6 +1,6 @@
from cpl.application import StartupABC
from cpl.core.configuration import ConfigurationABC
from cpl.dependency import ServiceProviderABC, ServiceCollection
from cpl.dependency import ServiceProvider, ServiceCollection
from cpl.core.environment import Environment
@@ -12,6 +12,6 @@ class Startup(StartupABC):
configuration.add_json_file("appsettings.json")
return configuration
def configure_services(self, services: ServiceCollection, environment: Environment) -> ServiceProviderABC:
def configure_services(self, services: ServiceCollection, environment: Environment) -> ServiceProvider:
services.add_translation()
return services.build()
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
# Optionaler Dev-Installationsmodus
dev_mode=false
if [[ "${1:-}" == "-dev" ]]; then
dev_mode=true
fi
shopt -s nullglob
# Wähle die passende Requirements-Datei
pattern='requirements.txt'
msg_hint='src/*/requirements.txt'
if $dev_mode; then
pattern='requirements.dev.txt'
msg_hint='src/*/requirements.dev.txt'
fi
req_files=(src/*/"$pattern")
if ((${#req_files[@]} == 0)); then
echo "Keine Requirements-Dateien gefunden unter '$msg_hint'" >&2
exit 1
fi
tmp_combined="$(mktemp)"
trap 'rm -f "$tmp_combined"' EXIT
# Kombiniere, filtere Kommentare/Whitespace, entferne cpl-*, dedupliziere.
awk '
function trim(s){ sub(/^[[:space:]]+/,"",s); sub(/[[:space:]]+$/,"",s); return s }
{
line=$0
if (line ~ /^[[:space:]]*#/) next
sub(/#[^!].*$/,"",line)
line=trim(line)
if (line == "") next
e = line
sub(/^-e[[:space:]]+/,"",e)
token = e
sub(/\[.*/,"",token)
n = split(token, a, /[<>=!~;@[:space:]]/)
name = tolower(a[1])
if (name ~ /^cpl-/) next
print line
}
' "${req_files[@]}" | sort -u > "$tmp_combined"
if ! [ -s "$tmp_combined" ]; then
echo "Nichts zu installieren nach dem Entfernen von cpl-* Paketen." >&2
exit 0
fi
echo "Installiere Abhängigkeiten (ohne cpl-*) aus:"
printf ' - %s\n' "${req_files[@]}"
echo
echo "Finale Menge zur Installation:"
cat "$tmp_combined"
echo
python -m pip install -r "$tmp_combined"
+17 -1
View File
@@ -1,2 +1,18 @@
[tool.black]
line-length = 120
line-length = 120
[tool.pytest.ini_options]
pythonpath = [
"src/api",
"src/application",
"src/auth",
"src/cli",
"src/core",
"src/database",
"src/dependency",
"src/graphql",
"src/mail",
"src/query",
"src/translation"
]
testpaths = ["test"]
+6
View File
@@ -0,0 +1,6 @@
from .error import APIError, AlreadyExists, EndpointNotImplemented, Forbidden, NotFound, Unauthorized
from .logger import APILogger
from .settings import ApiSettings
from .api_module import ApiModule
__version__ = "1.0.0"
+1
View File
@@ -0,0 +1 @@
from .asgi_middleware_abc import ASGIMiddleware
@@ -0,0 +1,15 @@
from abc import ABC, abstractmethod
from starlette.types import Scope, Receive, Send
class ASGIMiddleware(ABC):
@abstractmethod
def __init__(self, app):
self._app = app
def _call_next(self, scope: Scope, receive: Receive, send: Send):
return self._app(scope, receive, send)
@abstractmethod
async def __call__(self, scope: Scope, receive: Receive, send: Send): ...
+45
View File
@@ -0,0 +1,45 @@
from abc import ABC
from enum import Enum
from typing import Self
from starlette.applications import Starlette
from cpl.api.model.api_route import ApiRoute
from cpl.api.model.validation_match import ValidationMatch
from cpl.api.typing import HTTPMethods, PartialMiddleware, TEndpoint, PolicyInput
from cpl.application.abc.application_abc import ApplicationABC
from cpl.dependency.service_provider import ServiceProvider
from cpl.dependency.typing import Modules
class WebAppABC(ApplicationABC, ABC):
def __init__(self, services: ServiceProvider, modules: Modules, required_modules: list[str | object] = None):
ApplicationABC.__init__(self, services, modules, required_modules)
def with_routes_directory(self, directory: str) -> Self: ...
def with_app(self, app: Starlette) -> Self: ...
def with_routes(
self,
routes: list[ApiRoute],
method: HTTPMethods,
authentication: bool = False,
roles: list[str | Enum] = None,
permissions: list[str | Enum] = None,
policies: list[str] = None,
match: ValidationMatch = None,
) -> Self: ...
def with_route(
self,
path: str,
fn: TEndpoint,
method: HTTPMethods,
authentication: bool = False,
roles: list[str | Enum] = None,
permissions: list[str | Enum] = None,
policies: list[str] = None,
match: ValidationMatch = None,
) -> Self: ...
def with_middleware(self, middleware: PartialMiddleware) -> Self: ...
def with_authentication(self) -> Self: ...
def with_authorization(self, *policies: list[PolicyInput] | PolicyInput) -> Self: ...
+22
View File
@@ -0,0 +1,22 @@
from cpl.api import ApiSettings
from cpl.api.registry.policy import PolicyRegistry
from cpl.api.registry.route import RouteRegistry
from cpl.auth.auth_module import AuthModule
from cpl.auth.permission.permission_module import PermissionsModule
from cpl.database.database_module import DatabaseModule
from cpl.dependency import ServiceCollection
from cpl.dependency.module.module import Module
class ApiModule(Module):
config = [ApiSettings]
singleton = [
PolicyRegistry,
RouteRegistry,
]
@staticmethod
def register(collection: ServiceCollection):
collection.add_module(DatabaseModule)
collection.add_module(AuthModule)
collection.add_module(PermissionsModule)
+1
View File
@@ -0,0 +1 @@
from .web_app import WebApp
+275
View File
@@ -0,0 +1,275 @@
import os
from enum import Enum
from typing import Mapping, Any, Self
import uvicorn
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.types import ExceptionHandler
from cpl.api.abc.web_app_abc import WebAppABC
from cpl.api.api_module import ApiModule
from cpl.api.error import APIError
from cpl.api.logger import APILogger
from cpl.api.middleware.authentication import AuthenticationMiddleware
from cpl.api.middleware.authorization import AuthorizationMiddleware
from cpl.api.middleware.logging import LoggingMiddleware
from cpl.api.middleware.request import RequestMiddleware
from cpl.api.model.api_route import ApiRoute
from cpl.api.model.policy import Policy
from cpl.api.model.validation_match import ValidationMatch
from cpl.api.registry.policy import PolicyRegistry
from cpl.api.registry.route import RouteRegistry
from cpl.api.router import Router
from cpl.api.settings import ApiSettings
from cpl.api.typing import HTTPMethods, PartialMiddleware, TEndpoint, PolicyInput
from cpl.auth.auth_module import AuthModule
from cpl.auth.permission.permission_module import PermissionsModule
from cpl.core.configuration.configuration import Configuration
from cpl.dependency.inject import inject
from cpl.dependency.service_provider import ServiceProvider
from cpl.dependency.typing import Modules
class WebApp(WebAppABC):
def __init__(self, services: ServiceProvider, modules: Modules, required_modules: list[str | object] = None):
WebAppABC.__init__(
self, services, modules, [AuthModule, PermissionsModule, ApiModule] + (required_modules or [])
)
self._app: Starlette | None = None
self._logger = services.get_service(APILogger)
self._api_settings = Configuration.get(ApiSettings)
self._policies = services.get_service(PolicyRegistry)
self._routes = services.get_service(RouteRegistry)
self._middleware: list[Middleware] = []
self._exception_handlers: Mapping[Any, ExceptionHandler] = {
Exception: self._handle_exception,
APIError: self._handle_exception,
}
self.with_middleware(RequestMiddleware)
self.with_middleware(LoggingMiddleware)
async def _handle_exception(self, request: Request, exc: Exception):
if isinstance(exc, APIError):
self._logger.error(exc)
return JSONResponse({"error": str(exc)}, status_code=exc.status_code)
if hasattr(request.state, "request_id"):
self._logger.error(f"Request {request.state.request_id}", exc)
else:
self._logger.error("Request unknown", exc)
return JSONResponse({"error": str(exc)}, status_code=500)
def _get_allowed_origins(self):
origins = self._api_settings.allowed_origins
if origins is None or origins == "":
self._logger.warning("No allowed origins specified, allowing all origins")
return ["*"]
self._logger.debug(f"Allowed origins: {origins}")
return origins.split(",")
def _check_for_app(self):
if self._app is not None:
raise ValueError("App is already set, cannot add routes or middleware")
def _validate_policies(self):
for rule in Router.get_authorization_rules():
for policy_name in rule["policies"]:
policy = self._policies.get(policy_name)
if not policy:
self._logger.fatal(f"Authorization policy '{policy_name}' not found")
def with_routes_directory(self, directory: str) -> Self:
self._check_for_app()
assert directory is not None, "directory must not be None"
base = directory.replace("/", ".").replace("\\", ".")
for filename in os.listdir(directory):
if not filename.endswith(".py") or filename == "__init__.py":
continue
__import__(f"{base}.{filename[:-3]}")
return self
def with_app(self, app: Starlette) -> Self:
assert app is not None, "app must not be None"
assert isinstance(app, Starlette), "app must be an instance of Starlette"
self._app = app
return self
def with_routes(
self,
routes: list[ApiRoute],
method: HTTPMethods,
authentication: bool = False,
roles: list[str | Enum] = None,
permissions: list[str | Enum] = None,
policies: list[str] = None,
match: ValidationMatch = None,
) -> Self:
self._check_for_app()
assert self._routes is not None, "routes must not be None"
assert all(isinstance(route, ApiRoute) for route in routes), "all routes must be of type ApiRoute"
for route in routes:
self.with_route(
route.path,
route.fn,
method,
authentication,
roles,
permissions,
policies,
match,
)
return self
def with_route(
self,
path: str,
fn: TEndpoint,
method: HTTPMethods,
authentication: bool = False,
roles: list[str | Enum] = None,
permissions: list[str | Enum] = None,
policies: list[str] = None,
match: ValidationMatch = None,
) -> Self:
self._check_for_app()
assert path is not None, "path must not be None"
assert fn is not None, "fn must not be None"
assert method in [
"GET",
"HEAD",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
], "method must be a valid HTTP method"
Router.route(path, method, registry=self._routes)(fn)
if authentication:
Router.authenticate()(fn)
if roles or permissions or policies:
Router.authorize(roles, permissions, policies, match)(fn)
return self
def with_websocket(
self,
path: str,
fn: TEndpoint,
authentication: bool = False,
roles: list[str | Enum] = None,
permissions: list[str | Enum] = None,
policies: list[str] = None,
match: ValidationMatch = None,
) -> Self:
self._check_for_app()
assert path is not None, "path must not be None"
assert fn is not None, "fn must not be None"
Router.websocket(path, registry=self._routes)(fn)
if authentication:
Router.authenticate()(fn)
if roles or permissions or policies:
Router.authorize(roles, permissions, policies, match)(fn)
return self
def with_middleware(self, middleware: PartialMiddleware) -> Self:
self._check_for_app()
if isinstance(middleware, Middleware):
self._middleware.append(inject(middleware))
elif callable(middleware):
self._middleware.append(Middleware(inject(middleware)))
else:
raise ValueError("middleware must be of type starlette.middleware.Middleware or a callable")
return self
def with_authentication(self) -> Self:
self.with_middleware(AuthenticationMiddleware)
return self
def with_authorization(self, *policies: list[PolicyInput] | PolicyInput) -> Self:
self._check_for_app()
if policies:
_policies = []
if not isinstance(policies, list):
policies = list(policies)
for i, policy in enumerate(policies):
if isinstance(policy, dict):
for name, resolver in policy.items():
if not isinstance(name, str):
self._logger.warning(f"Skipping policy at index {i}, name must be a string")
continue
if not callable(resolver):
self._logger.warning(f"Skipping policy {name}, resolver must be callable")
continue
_policies.append(Policy(name, resolver))
continue
_policies.append(policy)
self._policies.extend(_policies)
self.with_middleware(AuthorizationMiddleware)
return self
async def _log_before_startup(self):
self._logger.info(f"Start API on {self._api_settings.host}:{self._api_settings.port}")
async def main(self):
self._logger.debug(f"Preparing API")
self._validate_policies()
if self._app is None:
routes = [route.to_starlette(inject) for route in self._routes.all()]
app = Starlette(
routes=routes,
middleware=[
*self._middleware,
Middleware(
CORSMiddleware,
allow_origins=self._get_allowed_origins(),
allow_methods=["*"],
allow_headers=["*"],
),
],
exception_handlers=self._exception_handlers,
)
else:
app = self._app
await self._log_before_startup()
config = uvicorn.Config(
app, host=self._api_settings.host, port=self._api_settings.port, log_config=None, loop="asyncio"
)
server = uvicorn.Server(config)
await server.serve()
self._logger.info("Shutdown API")

Some files were not shown because too many files have changed in this diff Show More