Compare commits
757 Commits
2021.4.1
...
2025.09.15
| Author | SHA1 | Date | |
|---|---|---|---|
| 5edabbf8c1 | |||
| 62b6435470 | |||
| d9d7802f33 | |||
| 25b4ca0696 | |||
| 3b120370b8 | |||
| aac038ef63 | |||
| 784632a0b4 | |||
| 4719c32457 | |||
| 516fa3fb7e | |||
| 2d2bb86720 | |||
| c1e65bf0dc | |||
| 688381b106 | |||
| c3e3367264 | |||
| c3d51e29fc | |||
| cae2e6d92e | |||
| 347418bdc5 | |||
| 153fc8922e | |||
| cdbfaeb013 | |||
| 4062a6ebe4 | |||
| 46ef6b3938 | |||
| c1b9c0fb4a | |||
| 8ede2998fe | |||
| 41f974fe6e | |||
| e7af5ce322 | |||
| 7881294554 | |||
| b3fd122fcd | |||
| 1dd48899d7 | |||
| f357e97ce5 | |||
| 2d8ecd424a | |||
| affbbc822d | |||
| 43ea732c5f | |||
| 708918464f | |||
| ed2377c288 | |||
| b819eb439c | |||
| b3ed7101fc | |||
| a3504af376 | |||
| 81c2f1cba9 | |||
| bea5b22251 | |||
| e04d0beb68 | |||
| 08ba097aaa | |||
| ea968d94e4 | |||
| 8e12c72b07 | |||
| 8dee4d8f70 | |||
| 315b8e631a | |||
| cbb1860f25 | |||
| 9839bcaa14 | |||
| da54337221 | |||
| a6a1e764d1 | |||
| eb6aa08c10 | |||
| 14a190a67f | |||
| cf5ae89884 | |||
| 558dfb8ced | |||
| 2ec8fc22b3 | |||
| c94700495b | |||
| 69a3bc5e31 | |||
| d189f49418 | |||
| 60fb416b67 | |||
| 792429d19d | |||
| 106975015e | |||
| 1117735f2e | |||
| 0378f8944a | |||
| 75fde0f444 | |||
| 04f610c799 | |||
| 3178b59147 | |||
| 9c7008e179 | |||
| 7ff7dbc56b | |||
| 823d524a81 | |||
| 1b60debba7 | |||
| 3fc713cc8d | |||
| bb26c7e94d | |||
| 52404cf5d3 | |||
| 32223bcfc2 | |||
| 4e2bdb2b59 | |||
| d72514205c | |||
| 9b4afdbe18 | |||
| 6751541269 | |||
| fe9918af16 | |||
| d2c56f1a3f | |||
| 8624549aa6 | |||
| 9a6ce704b7 | |||
| 9af2a1962d | |||
| a8d4a7a362 | |||
| 943f5ad50b | |||
| 4cd8fea07a | |||
| 82b3a22ac8 | |||
| 2c6367a408 | |||
| af94a0c52d | |||
| 9c374f6bb2 | |||
| d0a19458c5 | |||
| 5d6e7677de | |||
| 01309e3124 | |||
| 083e1ce4a1 | |||
| 8c012e5902 | |||
| 5e6d17cc86 | |||
| c4a71de83f | |||
| 01bc746092 | |||
| 87d023ef7f | |||
| 119052f9a2 | |||
| 43710717c9 | |||
| 7bf5e25c5e | |||
| faa030df96 | |||
| b2efe285a9 | |||
| 9e28dce5ce | |||
| 8179508248 | |||
| 2c3664c1b9 | |||
| 269f083662 | |||
| 8b40c65661 | |||
| 7be3cf38dc | |||
| b18a7297a7 | |||
| 7f46fbe87a | |||
| e0ca7c2ae6 | |||
| 6389a940bc | |||
| 48d0daabf5 | |||
| 8fc3c04720 | |||
| 351f953fdb | |||
| e1560dfab5 | |||
| c9bc6a026a | |||
| 83f0e8bd42 | |||
| 7af83b49a5 | |||
| 59263ece6e | |||
| a507ed9f46 | |||
| 6b451142e2 | |||
| 12beea05a7 | |||
| 713fa5243c | |||
| d600852bec | |||
| c09f2f8e83 | |||
| 2fb515195c | |||
| 889f51f5c5 | |||
| 742ba8314a | |||
| b95d90a191 | |||
| 46fa49a102 | |||
| d5e89d6e2a | |||
| 086fc75fec | |||
| 05c33990bb | |||
| cbae40ef4d | |||
| 002ba6b4e0 | |||
| 85f75b4677 | |||
| 95d8df5bea | |||
| ac1cd9bdde | |||
| 2d9bb79af7 | |||
| 120c9e6800 | |||
| 6f3099a8db | |||
| e46711dc54 | |||
| 59920c59e0 | |||
| 604bb8f55c | |||
| 2f8dc63cdc | |||
| 4fbb3ec838 | |||
| 6818fe9a5c | |||
| 3854fa5aa6 | |||
| 1ca459e67a | |||
| fe8ffb2839 | |||
| 42fb88c35d | |||
| db7a0786a9 | |||
| f9f2612356 | |||
| df81bb599f | |||
| 6d2492c1cf | |||
| e244535557 | |||
| 5f10603fe5 | |||
| 6b8491eea2 | |||
| b63bf41294 | |||
| f6cc3a7ddf | |||
| d1c93abe2c | |||
| d6e3b37f7f | |||
| 9840779af4 | |||
| b893ec1fcf | |||
| d5e5bfe69d | |||
| 4afe5c4581 | |||
| e054ac364e | |||
| 68e01afb5b | |||
| 2e0a8bb1ff | |||
| b02aebef35 | |||
| 099a51ed93 | |||
| e4aedb354b | |||
| 301768b842 | |||
| 856960d799 | |||
| d0877a4ea6 | |||
| 3c20ab296a | |||
| 4dc7ee3314 | |||
| 05bd5e8593 | |||
| 2fe3912a07 | |||
| 2840628443 | |||
| f0f79e7e3b | |||
| e8ae635c88 | |||
| d8f7e03815 | |||
| ba1b5e49ae | |||
| 703a2c91b5 | |||
| 4a54bb62de | |||
| 9e84c8359b | |||
| 5139876d90 | |||
| 6aef49de40 | |||
| 186b336bf3 | |||
| 1bbec27d1a | |||
| f0a8d69e22 | |||
| 8bd237206c | |||
| 6bf7c3a2bc | |||
| 2e8be741cc | |||
| abd0352750 | |||
| 25d91b85f3 | |||
| f08eb42105 | |||
| f450102c93 | |||
| db28645e98 | |||
| 23efb776e7 | |||
| 7f621b282f | |||
| 5dc7fce1e7 | |||
| 879f59b0d5 | |||
| 511622820a | |||
| 0914f969e5 | |||
| c611045290 | |||
| 89ac9e90d6 | |||
| ae2a98b3bc | |||
| bd242baa0a | |||
| 8e4caeb2a8 | |||
| 3b55b3d5a5 | |||
| cc3b1afaa1 | |||
| a22b6fdbdb | |||
| e3041c4b5b | |||
| 8ad6d50a04 | |||
| 4db000f6d7 | |||
| 3821624c3b | |||
| 60a722b4ec | |||
| 65132aa107 | |||
| 48eac124a5 | |||
| be23b18d33 | |||
| 748cc66946 | |||
| 304b2e4255 | |||
| 9fa183f143 | |||
| 82df528395 | |||
| 49e3bddc91 | |||
| c52bf961e1 | |||
| df3f9f0306 | |||
| ef2d73aadc | |||
| 1ed721cccb | |||
| a724216563 | |||
| 2bcf4d7a97 | |||
| 2dfa2dd68f | |||
| e94ed0b7f0 | |||
| c7e2f8609c | |||
| 9d2321cc9c | |||
| 61d9912950 | |||
| f23d32a92a | |||
| dc489cbf7a | |||
| 41d24aae99 | |||
| 6b50bba139 | |||
| 139422fd32 | |||
| 409e11df42 | |||
| f3a11f9791 | |||
| 8dcc5b4011 | |||
| d0f65834f1 | |||
| 47ed0a705d | |||
| ae3192b63c | |||
| bb461f5fba | |||
| 52069b7bb3 | |||
| f0ed0bd2e1 | |||
| e868a120f0 | |||
| 70652aeb4c | |||
| 28adcc4e49 | |||
| affbb1ee7b | |||
| d8a4210c9b | |||
| b16d555e33 | |||
| 4380142ccd | |||
| 91f2d6cb21 | |||
| 249b071ee9 | |||
| 53fe437100 | |||
| fc63f3616e | |||
| a528511732 | |||
| dbe1d37046 | |||
| 988965ed52 | |||
| 3c5ca594d5 | |||
| 452b30c899 | |||
| 85c5b26fdf | |||
| 92ed684866 | |||
| 88e83db330 | |||
| 55609fc691 | |||
| e0d89f967f | |||
| b2ce8a6924 | |||
| 71efbd1b42 | |||
| e36939a108 | |||
| 721461fc1b | |||
| 72ee59b2b4 | |||
| 95159da0fc | |||
| 330d702347 | |||
| cbf669d3bd | |||
| 7498fe3255 | |||
| 880998e8ca | |||
| ecc9657eaa | |||
| 125f65cf1b | |||
| 1cd9c7d669 | |||
| 5165f31066 | |||
| 10d33e5d1d | |||
| fb35e8b8b2 | |||
| 4402bcce73 | |||
| 8839b26f00 | |||
| a79358725c | |||
| 2ab1576230 | |||
| c0be380f69 | |||
| c0ae0c8389 | |||
| 4a766e8284 | |||
| 2772ea8df2 | |||
| a604f0e821 | |||
| 6995b25518 | |||
| e4f843829f | |||
| 39b6ca790c | |||
| 4c8d7a8031 | |||
| 6d75bc433e | |||
| 8bb7fbd1c4 | |||
| ad03e655e9 | |||
| 61d3974a47 | |||
| 3d852c87e2 | |||
| 0e46528f32 | |||
| bb8fa1f6a8 | |||
| 9fdaa810c0 | |||
| 4083ac0c14 | |||
| b9e0b7ccd0 | |||
| 4b9e428abb | |||
| 63ad13e09a | |||
| cc06a308a1 | |||
| e9fdd20181 | |||
| 8692686cb5 | |||
| db29ad36fe | |||
| 0710b25317 | |||
| cbd2964f42 | |||
| f51118f13c | |||
| 323e363b42 | |||
| e8a4fe6142 | |||
| 587d9b359c | |||
| c2632253ac | |||
| 45be77dd10 | |||
| 78ffbcc33d | |||
| d83d1a244e | |||
| fa002a50ac | |||
| 32507afae9 | |||
| 6de3bbdaf4 | |||
| 6e50d0cb22 | |||
| 9e978f3ece | |||
| 32478926c8 | |||
| 2925788d01 | |||
| c0789cf4f7 | |||
| 89d591ce0a | |||
| 77c560b40c | |||
| dad4913bcd | |||
| 9b56650d4b | |||
| 9fca2018e5 | |||
| e5dd890931 | |||
| beadbf8e91 | |||
| ff5f9b833d | |||
| 162f06001a | |||
| 348fad24c7 | |||
| f9748cdb76 | |||
| 56380763a9 | |||
| 3a1630b687 | |||
| a593f34cde | |||
| 49cce5fa1a | |||
| df7bd06629 | |||
| e0da77fd4b | |||
| 833dd83008 | |||
| ce0ad4013f | |||
| 698f80adfe | |||
| d0b0f4bfcb | |||
| d23ea86ccf | |||
| 15d938b172 | |||
| 6c1d7a3b29 | |||
| c28f551682 | |||
| def92c78c8 | |||
| ddff9dec11 | |||
| 9e0e85d646 | |||
| b05ca7e980 | |||
| 8f8a9f7dd7 | |||
| df32978a31 | |||
| 79b51cf8c5 | |||
| a72dd0dc2e | |||
| d937c4c0e6 | |||
| 46cceb0910 | |||
| e74e39c189 | |||
| c2706c7737 | |||
| d0e66d24bd | |||
| c1a1849ad2 | |||
| 0675105731 | |||
| 7ee0b951a8 | |||
| d8168f024c | |||
| 2654dc7c69 | |||
| 4224960837 | |||
| 907c3a8fff | |||
| 28505479ba | |||
| 84d3abce3f | |||
| 9050dffc53 | |||
| e0f69c9bdf | |||
| bbd0a60f98 | |||
| e23f4187fe | |||
| 15f200df3a | |||
| a1a3ad6fb5 | |||
| d6677d53a3 | |||
| 07e43b50d1 | |||
| ac83555b24 | |||
| 1ab358059c | |||
| 5bf83c0d77 | |||
| 03ac00c6e3 | |||
| 0122d8fc78 | |||
| a66ce96dbf | |||
| 6da8e30446 | |||
| 539ed13be4 | |||
| 036a553f64 | |||
| 976a14584b | |||
| ed69f52927 | |||
| 98847495e6 | |||
| 40f132da33 | |||
| 5feb44595a | |||
| 48c6941f1a | |||
| a8756d493a | |||
| 9f4077a073 | |||
| 3e7a3d75c5 | |||
| 477d2d87d3 | |||
| 7a65dbe39b | |||
| f83755ac77 | |||
| a590cfd066 | |||
| f317035342 | |||
| 8900bdd446 | |||
| 35ecf158a2 | |||
| 353c1d30ec | |||
| 1c94fb5bd1 | |||
| cf305aeebb | |||
| 90e97433e0 | |||
| 125a85a0af | |||
| cfb8838c73 | |||
| 824d491ebc | |||
| d694c408c0 | |||
| d1bb266d49 | |||
| 419fcc3d29 | |||
| 24ab6a51c1 | |||
| dec4a45d98 | |||
| d11c56db03 | |||
| fc23315cc7 | |||
| 1f503fe20d | |||
| 4564f7ac0e | |||
| 7b823e1141 | |||
| dac3d9c6bb | |||
| ccca904cb8 | |||
| c66a81c0ec | |||
| fb810e2943 | |||
| 5cc663dedc | |||
| 593372699b | |||
| 1899cb5b86 | |||
| 0a285f2d28 | |||
| 94c82e6701 | |||
| 0d37c86061 | |||
| 6c8e9ccd89 | |||
| e7cf545502 | |||
| 44182eec86 | |||
| 0f5b1b7586 | |||
| 4fe073580a | |||
| 2fed654c16 | |||
| 8ebd4864d3 | |||
| a495532a4d | |||
| d87ce72f07 | |||
| 50201346e2 | |||
| 773b154371 | |||
| 5b59d1d384 | |||
| dcd8583183 | |||
| 97e52b39c0 | |||
| 581013b61a | |||
| fd68d25a1d | |||
| 588f93f7b1 | |||
| 96f402dcdd | |||
| c1b7fca8d5 | |||
| 2e4e7ccc57 | |||
| ada3d133df | |||
| 61e7b9d684 | |||
| 24172c658c | |||
| 62ac976b99 | |||
| 1f445a99b2 | |||
| 025286052b | |||
| 32a856f8e8 | |||
| 21f50f3b24 | |||
| ce77d41246 | |||
| dec137220c | |||
| 01c7e155c3 | |||
| dcbcb67ce8 | |||
| d57fd10718 | |||
| 2b91db5d81 | |||
| dffa3de1ec | |||
| 403837901b | |||
| 0de725c907 | |||
| e811e08c52 | |||
| 6518ae7516 | |||
| 95b5f99f1f | |||
| de22e79b71 | |||
| 127c67e566 | |||
| 3523dbd5c5 | |||
| b526a07e19 | |||
| d9f10edbb7 | |||
| 7616cd4c69 | |||
| afe75f6848 | |||
| c0e2db8281 | |||
| 3727af11c4 | |||
| 450295cfcf | |||
| 457bce8aa9 | |||
| 0f81497ea3 | |||
| fa7ee800e1 | |||
| 56b7452e55 | |||
| 9d73566453 | |||
| 28ba0c174a | |||
| 5d0444bcc0 | |||
| 40876005d5 | |||
| ac6d941822 | |||
| 113b365a47 | |||
| 2f785536e1 | |||
| 9ab4013b4b | |||
| b3b04f03ce | |||
| 57040f85f0 | |||
| 589ef4fd4c | |||
| 4b829cc484 | |||
| 98e343a108 | |||
| 7749d5a789 | |||
| c3b48bfa41 | |||
| 470e7ab2f4 | |||
| 4ceea36926 | |||
| b3f8174663 | |||
| 58dccea28b | |||
| ca086e01d7 | |||
| c5b1a95661 | |||
| d7881774a8 | |||
| 6eec92243e | |||
| 124b236182 | |||
| 2dc96cdf3d | |||
| 6f7923debc | |||
| 90d148b4a5 | |||
| 73e2c5ec10 | |||
| f31522dcd9 | |||
| a83607fd81 | |||
| 707df3f27d | |||
| 0f43e57831 | |||
| c08ab33060 | |||
| ee2abd98b3 | |||
| e39661a42a | |||
| 9518d19580 | |||
| adf95faaf8 | |||
| 2f92e9909d | |||
| b18f2c4bcd | |||
| 784ba049ad | |||
| 4d6d19d2e5 | |||
| 6fa47f19af | |||
| fb2872422e | |||
| 8460c0fa33 | |||
| 18f95341f4 | |||
| a8011267d9 | |||
| f8ebb4f1d7 | |||
| 3903f85368 | |||
| 05a9bdbf4a | |||
| 2809a9cd82 | |||
| 1c6794dc40 | |||
| cac80657df | |||
| 11241d8f99 | |||
| 308e5c9b0c | |||
| c12ff4f9e3 | |||
| 091c89df2c | |||
| 63a67be0ee | |||
| c68224b31a | |||
| 9f219757c3 | |||
| e09a4402b3 | |||
| 6867eb0e65 | |||
| ffaa5c4a2b | |||
| b9499108d8 | |||
| 37175b7227 | |||
| 2b5831c5fe | |||
| 62c8b9c68d | |||
| a080119c44 | |||
| a26c6f1bd9 | |||
| daac14e099 | |||
| d4b5c32a21 | |||
| c115afd736 | |||
| e7863a92e0 | |||
| 82a9e3a23a | |||
| a3a0a150a8 | |||
| 6951a19670 | |||
| 669133d491 | |||
| c60598b3f6 | |||
| 45733b30ef | |||
| f8bd86692e | |||
| 5e5d86cf94 | |||
| e0b7728719 | |||
| 0f85d4b9bc | |||
| a3fff9c7d7 | |||
| 8dcf3a6768 | |||
| 94949394de | |||
| b7be439381 | |||
| 0cae3428b9 | |||
| cc7755bafc | |||
| 4e78b9c12b | |||
| 5c80cf2222 | |||
| d02548e1a8 | |||
| 51efe973e8 | |||
| 0bb272d89b | |||
| 59f6843ea9 | |||
| be6838f97b | |||
| d5b2d3605b | |||
| 7a48917fb4 | |||
| 59832742b6 | |||
| f9f04901c0 | |||
| 593ffbb068 | |||
| 3d35f2ac63 | |||
| 5bfba581cb | |||
| 8fb570468c | |||
| 128fda60b0 | |||
| ab539c15dd | |||
| b931ba6e17 | |||
| f611aa73db | |||
| 69f697e36f | |||
| 654fb69295 | |||
| 850d44a105 | |||
| 8d4597705d | |||
| fa5655414a | |||
| 6fc4cebbfe | |||
| 89f3c5833f | |||
| adf9a33e83 | |||
| 4adf6bc834 | |||
| 9026f021ed | |||
| e562c31033 | |||
| 7b90b5af57 | |||
| cda1fabd4f | |||
| 4209055d62 | |||
| ed1a4eb88c | |||
| 478f1ad0ff | |||
| 80eb1da0e1 | |||
| ae39d43ea9 | |||
| 602c48bc06 | |||
| d34d501520 | |||
| 8f3ee53915 | |||
| 7eca382cb3 | |||
| f8f5d46491 | |||
| 696c62292b | |||
| d94b25d9d8 | |||
| 93829027af | |||
| cce4b6b191 | |||
| 0876f06282 | |||
| c970412a66 | |||
| 6af9328e14 | |||
| 5987b71c80 | |||
| 79fdc0c60b | |||
| cfb6a84799 | |||
| faa7e701fd | |||
| d5b8286f68 | |||
| a3d5737eaf | |||
| 314bb1b722 | |||
| 1c353bbcdf | |||
| 036b9dcc2d | |||
| 0fec5719bc | |||
| 3572081e43 | |||
| 8cf7f494b3 | |||
| b1165e169a | |||
| 284ebe1b11 | |||
| d51659db3e | |||
| 3aa3a874c9 | |||
| 113d3199b1 | |||
| 5712a9ad67 | |||
| 1f8d420749 | |||
| 9065ef901c | |||
| b2c5d304f8 | |||
| 70ae7dd9e6 | |||
| 94b122d677 | |||
| f6fc5d09e7 | |||
| 2b5c75cc67 | |||
| 167f813bcf | |||
| 7b9765e5b1 | |||
| 7ad01d4bdd | |||
| d19c47e571 | |||
| 4bfc647b91 | |||
| 98117ed7ad | |||
| cc9b25356c | |||
| a95d6e7f8a | |||
| fe5c41ac09 | |||
| b672174960 | |||
| a8691ced5b | |||
| fc53f90ecd | |||
| 0f836b5c7d | |||
| 34ed7123d1 | |||
| 5f668e17c9 | |||
| 806888bf49 | |||
| e8756a09ca | |||
| e38988be1b | |||
| 4009c93353 | |||
| 3ba5c10fad | |||
| 2552f1d43c | |||
| 5be2e83798 | |||
| d4919e027c | |||
| 7ef238b64d | |||
| 703d20f43f | |||
| d9bd7911ef | |||
| 6a5c832288 | |||
| dc849c4f71 | |||
| 76a6ba11fb | |||
| 305a19730f | |||
| de15e81703 | |||
| b91544a0da | |||
| caca34168e | |||
| 18b8ac589d | |||
| d85e628784 | |||
| 1e150cf10c | |||
| 9b0f0b7559 | |||
| cc118b5030 | |||
| 3e7ff78861 | |||
| 448d538d21 | |||
| d368161eb3 | |||
| eef8488ca2 | |||
| b22d04b145 | |||
| 6579e54c2b | |||
| 88c0153ca2 | |||
| f28c6ab309 | |||
| 976041f812 | |||
| 7693dd462f | |||
| df5e61cf98 | |||
| df87d08be3 | |||
| 33cb35e26f | |||
| 18b2cd8f2f | |||
| 7ab2a881e8 | |||
| 960de880ee | |||
| c0d8904782 | |||
| b6d508526a | |||
| 31307485f0 | |||
| 612c3583f1 | |||
| 6cdd3376f1 | |||
| b947789779 | |||
| 0da83935fc | |||
| 3428c70b69 | |||
| 3d511ff727 | |||
| cf2f293290 | |||
| c39601a35d | |||
| 5f36e0a43e | |||
| 31fe1c5cd7 | |||
| 0cdecd893a | |||
| 260fe06be8 | |||
| 8ddfd35a82 | |||
| 099bae3d7c | |||
| 9758e1fae2 | |||
| 211143d6ab | |||
| 70b49a098a | |||
| e2544f90fe | |||
| c223524d1b | |||
| 1789e2671a | |||
| d0812bcf5c | |||
| 4d5368e5fc | |||
| f3be927440 | |||
| d0e3e1792f | |||
| 31793fb290 | |||
| 41ddc86961 | |||
| 30ef74dd18 | |||
| bc69476bb2 | |||
| 05b9fd801b | |||
| 7af7f866c1 | |||
| 069e44bee6 | |||
| 3c0dd00a99 | |||
| afb91c8f1f | |||
| ca51f88d2b | |||
| a683b70c5f | |||
| c650e87443 | |||
| 36dfedf918 | |||
| 252f2b55fe | |||
| 99091a2540 | |||
| c3fdb7a049 |
41
.gitea/workflows/build-dev.yaml
Normal file
41
.gitea/workflows/build-dev.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Build on push
|
||||
run-name: Build on push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
uses: ./.gitea/workflows/prepare.yaml
|
||||
with:
|
||||
version_suffix: 'dev'
|
||||
secrets: inherit
|
||||
|
||||
core:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-core
|
||||
secrets: inherit
|
||||
|
||||
query:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-query
|
||||
secrets: inherit
|
||||
|
||||
translation:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cpl-translation
|
||||
secrets: inherit
|
||||
|
||||
mail:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cpl-mail
|
||||
secrets: inherit
|
||||
39
.gitea/workflows/build.yaml
Normal file
39
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Build on push
|
||||
run-name: Build on push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
uses: ./.gitea/workflows/prepare.yaml
|
||||
secrets: inherit
|
||||
|
||||
core:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-core
|
||||
secrets: inherit
|
||||
|
||||
query:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-query
|
||||
secrets: inherit
|
||||
|
||||
translation:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cpl-translation
|
||||
secrets: inherit
|
||||
|
||||
mail:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cpl-mail
|
||||
secrets: inherit
|
||||
65
.gitea/workflows/package.yaml
Normal file
65
.gitea/workflows/package.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Build Package
|
||||
run-name: Build Python Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version_suffix:
|
||||
description: 'Suffix for version (z.B. "dev", "alpha", "beta")'
|
||||
required: false
|
||||
type: string
|
||||
working_directory:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ runner ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ inputs.working_directory }}
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Download build version artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: version
|
||||
|
||||
- name: Set version
|
||||
run: |
|
||||
sed -i -E "s/^version = \".*\"/version = \"$(cat /workspace/sh-edraft.de/cpl/version.txt)\"/" pyproject.toml
|
||||
echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)"
|
||||
cat pyproject.toml
|
||||
|
||||
- name: Set pip conf
|
||||
run: |
|
||||
cat > .pip.conf <<'EOF'
|
||||
[global]
|
||||
extra-index-url = https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/
|
||||
EOF
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
export PIP_CONFIG_FILE=".pip.conf"
|
||||
pip install build
|
||||
|
||||
- name: Build Package
|
||||
run: |
|
||||
python -m build --outdir dist
|
||||
|
||||
- name: Login to registry git.sh-edraft.de
|
||||
uses: https://github.com/docker/login-action@v1
|
||||
with:
|
||||
registry: git.sh-edraft.de
|
||||
username: ${{ secrets.CI_USERNAME }}
|
||||
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Push image
|
||||
run: |
|
||||
pip install twine
|
||||
python -m twine upload --repository-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi -u ${{ secrets.CI_USERNAME }} -p ${{ secrets.CI_ACCESS_TOKEN }} ./dist/*
|
||||
54
.gitea/workflows/prepare.yaml
Normal file
54
.gitea/workflows/prepare.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Prepare Build
|
||||
run-name: Prepare Build Version
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version_suffix:
|
||||
description: 'Suffix for version (z.B. "dev", "alpha", "beta")'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
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: Get Date and Build Number
|
||||
run: |
|
||||
git fetch --tags
|
||||
git tag
|
||||
DATE=$(date +'%Y.%m.%d')
|
||||
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
|
||||
BUILD_NUMBER=$(($TAG_COUNT + 1))
|
||||
|
||||
VERSION_SUFFIX=${{ inputs.version_suffix }}
|
||||
if [ -n "$VERSION_SUFFIX" ] && [ "$VERSION_SUFFIX" = "dev" ]; then
|
||||
BUILD_VERSION="${DATE}.dev${BUILD_NUMBER}"
|
||||
elif [ -n "$VERSION_SUFFIX" ]; then
|
||||
BUILD_VERSION="${DATE}.${BUILD_NUMBER}${VERSION_SUFFIX}"
|
||||
else
|
||||
BUILD_VERSION="${DATE}.${BUILD_NUMBER}"
|
||||
fi
|
||||
|
||||
echo "$BUILD_VERSION" > version.txt
|
||||
echo "VERSION $BUILD_VERSION"
|
||||
|
||||
- name: Create Git Tag for Build
|
||||
run: |
|
||||
git config user.name "ci"
|
||||
git config user.email "dev@sh-edraft.de"
|
||||
echo "tag $(cat version.txt)"
|
||||
git tag $(cat version.txt)
|
||||
git push origin --tags
|
||||
|
||||
- name: Upload build version artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: version
|
||||
path: version.txt
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -106,10 +106,14 @@ celerybeat.pid
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
venv_*/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Custom Environments
|
||||
cpl-env/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
@@ -129,5 +133,8 @@ dmypy.json
|
||||
.pyre/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
PythonImportHelper-v2-Completion.json
|
||||
|
||||
# cpl unittest stuff
|
||||
unittests/test_*_playground
|
||||
|
||||
2
.pip.conf
Normal file
2
.pip.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
[global]
|
||||
extra-index-url = https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
MIT License Copyright (c) <year> <copyright holders>
|
||||
MIT License Copyright (c) 2020 - 2023 sh-edraft.de
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
153
README.md
153
README.md
@@ -1,2 +1,153 @@
|
||||
# sh_common_py_lib
|
||||
<h1 align="center">CPL - Common python library</h1>
|
||||
|
||||
<!-- Summary -->
|
||||
<p align="center">
|
||||
<!-- <img src="" alt="cpl-logo" width="120px" height="120px"/> -->
|
||||
<br>
|
||||
<i>
|
||||
CPL is a development platform for python server applications
|
||||
<br>using Python.</i>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
## Table of Contents
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<ol>
|
||||
<li><a href="#Features">Features</a></li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#roadmap">Roadmap</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ol>
|
||||
|
||||
## Features
|
||||
<!-- FEATURE OVERVIEW -->
|
||||
- Expandle
|
||||
- Application base
|
||||
- Standardized application classes
|
||||
- Application object builder
|
||||
- Application extension classes
|
||||
- Startup classes
|
||||
- Startup extension classes
|
||||
- Configuration
|
||||
- Configure via object mapped JSON
|
||||
- Console argument handling
|
||||
- Console class for in and output
|
||||
- Banner
|
||||
- Spinner
|
||||
- Options (menu)
|
||||
- Table
|
||||
- Write
|
||||
- Write_at
|
||||
- Write_line
|
||||
- Write_line_at
|
||||
- Dependency injection
|
||||
- Service lifetimes: singleton, scoped and transient
|
||||
- Providing of application environment
|
||||
- Environment (development, staging, testing, production)
|
||||
- Appname
|
||||
- Customer
|
||||
- Hostname
|
||||
- Runtime directory
|
||||
- Working directory
|
||||
- Logging
|
||||
- Standardized logger
|
||||
- Log-level (FATAL, ERROR, WARN, INFO, DEBUG & TRACE)
|
||||
- Mail handling
|
||||
- Send mails
|
||||
- Pipe classes
|
||||
- Convert input
|
||||
- Utils
|
||||
- Credential manager
|
||||
- Encryption via BASE64
|
||||
- PIP wrapper class based on subprocess
|
||||
- Run pip commands
|
||||
- String converter to different variants
|
||||
- to_lower_case
|
||||
- to_camel_case
|
||||
- ...
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
[Get started with CPL][quickstart].
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Install [python] which includes [Pip installs packages][pip]
|
||||
|
||||
### Installation
|
||||
|
||||
Install the CPL package
|
||||
```sh
|
||||
pip install cpl-core --extra-index-url https://pip.sh-edraft.de
|
||||
```
|
||||
|
||||
Install the CPL CLI
|
||||
```sh
|
||||
pip install cpl-cli --extra-index-url https://pip.sh-edraft.de
|
||||
```
|
||||
|
||||
Create workspace:
|
||||
```sh
|
||||
cpl new <console|library|unittest> <PROJECT NAME>
|
||||
```
|
||||
|
||||
Run the application:
|
||||
```sh
|
||||
cd <PROJECT NAME>
|
||||
cpl start
|
||||
```
|
||||
|
||||
|
||||
<!-- ROADMAP -->
|
||||
## Roadmap
|
||||
|
||||
See the [open issues](https://git.sh-edraft.de/sh-edraft.de/sh_cpl/issues) for a list of proposed features (and known issues).
|
||||
|
||||
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
|
||||
### Contributing Guidelines
|
||||
|
||||
Read through our [contributing guidelines][contributing] to learn about our submission process, coding rules and more.
|
||||
|
||||
### Want to Help?
|
||||
|
||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for [contributing][contributing].
|
||||
|
||||
|
||||
|
||||
<!-- LICENSE -->
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See [LICENSE] for more information.
|
||||
|
||||
|
||||
|
||||
<!-- CONTACT -->
|
||||
## Contact
|
||||
|
||||
Sven Heidemann - sven.heidemann@sh-edraft.de
|
||||
|
||||
Project link: [https://git.sh-edraft.de/sh-edraft.de/sh_common_py_lib](https://git.sh-edraft.de/sh-edraft.de/sh_cpl)
|
||||
|
||||
<!-- External LINKS -->
|
||||
[pip_url]: https://pip.sh-edraft.de
|
||||
[python]: https://www.python.org/
|
||||
[pip]: https://pypi.org/project/pip/
|
||||
|
||||
<!-- Internal LINKS -->
|
||||
[project]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl
|
||||
[quickstart]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl/wiki/quickstart
|
||||
[contributing]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl/wiki/contributing
|
||||
[license]: LICENSE
|
||||
|
||||
56
cpl.json
56
cpl.json
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "sh_cpl",
|
||||
"Version": {
|
||||
"Major": "2021",
|
||||
"Minor": "04",
|
||||
"Micro": "01-15"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
"Description": "sh-edraft Common Python library",
|
||||
"LongDescription": "sh-edraft Common Python library",
|
||||
"URL": "https://www.sh-edraft.de",
|
||||
"CopyrightDate": "2020 - 2021",
|
||||
"CopyrightName": "sh-edraft.de",
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"colorama==0.4.4",
|
||||
"mysql-connector==2.2.9",
|
||||
"psutil==5.8.0",
|
||||
"packaging==20.9",
|
||||
"pyfiglet==0.8.post1",
|
||||
"pynput==1.7.3",
|
||||
"SQLAlchemy==1.4.0",
|
||||
"setuptools==54.1.2",
|
||||
"tabulate==0.8.9",
|
||||
"termcolor==1.1.0",
|
||||
"watchdog==2.0.2",
|
||||
"wheel==0.36.2"
|
||||
],
|
||||
"PythonVersion": ">=3.8",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"SourcePath": "src",
|
||||
"OutputPath": "dist",
|
||||
"Main": "cpl_cli.main",
|
||||
"EntryPoint": "cpl",
|
||||
"IncludePackageData": true,
|
||||
"Included": [
|
||||
"*/templates"
|
||||
],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {
|
||||
"cpl_cli": [
|
||||
"*.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
19
docs/cli.txt
19
docs/cli.txt
@@ -1,19 +0,0 @@
|
||||
prefix: cpl
|
||||
commands:
|
||||
build
|
||||
|
||||
generate:
|
||||
abc | a
|
||||
class | c
|
||||
configmodel | cm
|
||||
enum | e
|
||||
service | s
|
||||
|
||||
help
|
||||
new
|
||||
console
|
||||
|
||||
start
|
||||
publish
|
||||
update
|
||||
version
|
||||
@@ -1,2 +0,0 @@
|
||||
python setup.py install # for install
|
||||
python setup.py sdist bdist_wheel # for build
|
||||
11
docs/pip.txt
11
docs/pip.txt
@@ -1,11 +0,0 @@
|
||||
upload:
|
||||
prod:
|
||||
twine upload --repository-url https://pip.sh-edraft.de dist/publish/setup/*
|
||||
twine upload -r pip.sh-edraft.de dist/publish/setup/*
|
||||
|
||||
dev:
|
||||
twine upload --repository-url https://pip-dev.sh-edraft.de dist/publish/setup/*
|
||||
twine upload -r pip-dev.sh-edraft.de dist/publish/setup/*
|
||||
|
||||
install:
|
||||
pip install --extra-index-url https://pip.sh-edraft.de/ sh_cpl
|
||||
2
pyproject.toml
Normal file
2
pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
1
src/cpl-core/cpl/core/__init__.py
Normal file
1
src/cpl-core/cpl/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
6
src/cpl-core/cpl/core/application/__init__.py
Normal file
6
src/cpl-core/cpl/core/application/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .application_abc import ApplicationABC
|
||||
from .application_builder import ApplicationBuilder
|
||||
from .application_builder_abc import ApplicationBuilderABC
|
||||
from .application_extension_abc import ApplicationExtensionABC
|
||||
from .startup_abc import StartupABC
|
||||
from .startup_extension_abc import StartupExtensionABC
|
||||
60
src/cpl-core/cpl/core/application/application_abc.py
Normal file
60
src/cpl-core/cpl/core/application/application_abc.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class ApplicationABC(ABC):
|
||||
r"""ABC for the Application class
|
||||
|
||||
Parameters:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
Contains object loaded from appsettings
|
||||
services: :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
Contains instances of prepared objects
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
self._configuration: Optional[ConfigurationABC] = config
|
||||
self._environment: Optional[ApplicationEnvironmentABC] = self._configuration.environment
|
||||
self._services: Optional[ServiceProviderABC] = services
|
||||
|
||||
def run(self):
|
||||
r"""Entry point
|
||||
|
||||
Called by custom Application.main
|
||||
"""
|
||||
try:
|
||||
self.configure()
|
||||
self.main()
|
||||
except KeyboardInterrupt:
|
||||
Console.close()
|
||||
|
||||
async def run_async(self):
|
||||
r"""Entry point
|
||||
|
||||
Called by custom Application.main
|
||||
"""
|
||||
try:
|
||||
await self.configure()
|
||||
await self.main()
|
||||
except KeyboardInterrupt:
|
||||
Console.close()
|
||||
|
||||
@abstractmethod
|
||||
def configure(self):
|
||||
r"""Configure the application
|
||||
|
||||
Called by :class:`cpl.core.application.application_abc.ApplicationABC.run`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def main(self):
|
||||
r"""Custom entry point
|
||||
|
||||
Called by :class:`cpl.core.application.application_abc.ApplicationABC.run`
|
||||
"""
|
||||
91
src/cpl-core/cpl/core/application/application_builder.py
Normal file
91
src/cpl-core/cpl/core/application/application_builder.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from typing import Type, Optional, Callable, Union
|
||||
|
||||
from cpl.core.application.application_abc import ApplicationABC
|
||||
from cpl.core.application.application_builder_abc import ApplicationBuilderABC
|
||||
from cpl.core.application.application_extension_abc import ApplicationExtensionABC
|
||||
from cpl.core.application.async_application_extension_abc import AsyncApplicationExtensionABC
|
||||
from cpl.core.application.async_startup_abc import AsyncStartupABC
|
||||
from cpl.core.application.async_startup_extension_abc import AsyncStartupExtensionABC
|
||||
from cpl.core.application.startup_abc import StartupABC
|
||||
from cpl.core.application.startup_extension_abc import StartupExtensionABC
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.core.dependency_injection.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class ApplicationBuilder(ApplicationBuilderABC):
|
||||
r"""This is class is used to build an object of :class:`cpl.core.application.application_abc.ApplicationABC`
|
||||
|
||||
Parameter:
|
||||
app: Type[:class:`cpl.core.application.application_abc.ApplicationABC`]
|
||||
Application to build
|
||||
"""
|
||||
|
||||
def __init__(self, app: Type[ApplicationABC]):
|
||||
ApplicationBuilderABC.__init__(self)
|
||||
self._app = app
|
||||
self._startup: Optional[StartupABC | AsyncStartupABC] = None
|
||||
|
||||
self._configuration = Configuration()
|
||||
self._environment = self._configuration.environment
|
||||
self._services = ServiceCollection(self._configuration)
|
||||
|
||||
self._app_extensions: list[Type[ApplicationExtensionABC | AsyncApplicationExtensionABC]] = []
|
||||
self._startup_extensions: list[Type[StartupExtensionABC | AsyncStartupABC]] = []
|
||||
|
||||
def use_startup(self, startup: Type[StartupABC | AsyncStartupABC]) -> "ApplicationBuilder":
|
||||
self._startup = startup()
|
||||
return self
|
||||
|
||||
def use_extension(
|
||||
self, extension: Type[ApplicationExtensionABC | AsyncApplicationExtensionABC | StartupExtensionABC | AsyncStartupExtensionABC]
|
||||
) -> "ApplicationBuilder":
|
||||
if (issubclass(extension, ApplicationExtensionABC) or issubclass(extension, AsyncApplicationExtensionABC)) and extension not in self._app_extensions:
|
||||
self._app_extensions.append(extension)
|
||||
elif (issubclass(extension, StartupExtensionABC) or issubclass(extension, AsyncStartupExtensionABC)) and extension not in self._startup_extensions:
|
||||
self._startup_extensions.append(extension)
|
||||
|
||||
return self
|
||||
|
||||
def _build_startup(self):
|
||||
for ex in self._startup_extensions:
|
||||
extension = ex()
|
||||
extension.configure_configuration(self._configuration, self._environment)
|
||||
extension.configure_services(self._services, self._environment)
|
||||
|
||||
if self._startup is not None:
|
||||
self._startup.configure_configuration(self._configuration, self._environment)
|
||||
self._startup.configure_services(self._services, self._environment)
|
||||
|
||||
async def _build_async_startup(self):
|
||||
for ex in self._startup_extensions:
|
||||
extension = ex()
|
||||
await extension.configure_configuration(self._configuration, self._environment)
|
||||
await extension.configure_services(self._services, self._environment)
|
||||
|
||||
if self._startup is not None:
|
||||
await self._startup.configure_configuration(self._configuration, self._environment)
|
||||
await self._startup.configure_services(self._services, self._environment)
|
||||
|
||||
def build(self) -> ApplicationABC:
|
||||
self._build_startup()
|
||||
|
||||
config = self._configuration
|
||||
services = self._services.build_service_provider()
|
||||
|
||||
for ex in self._app_extensions:
|
||||
extension = ex()
|
||||
extension.run(config, services)
|
||||
|
||||
return self._app(config, services)
|
||||
|
||||
async def build_async(self) -> ApplicationABC:
|
||||
await self._build_async_startup()
|
||||
|
||||
config = self._configuration
|
||||
services = self._services.build_service_provider()
|
||||
|
||||
for ex in self._app_extensions:
|
||||
extension = ex()
|
||||
await extension.run(config, services)
|
||||
|
||||
return self._app(config, services)
|
||||
47
src/cpl-core/cpl/core/application/application_builder_abc.py
Normal file
47
src/cpl-core/cpl/core/application/application_builder_abc.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Type
|
||||
|
||||
from cpl.core.application.application_abc import ApplicationABC
|
||||
from cpl.core.application.startup_abc import StartupABC
|
||||
|
||||
|
||||
class ApplicationBuilderABC(ABC):
|
||||
r"""ABC for the :class:`cpl.core.application.application_builder.ApplicationBuilder`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def use_startup(self, startup: Type[StartupABC]):
|
||||
r"""Sets the custom startup class to use
|
||||
|
||||
Parameter:
|
||||
startup: Type[:class:`cpl.core.application.startup_abc.StartupABC`]
|
||||
Startup class to use
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def use_startup(self, startup: Type[StartupABC]):
|
||||
r"""Sets the custom startup class to use async
|
||||
|
||||
Parameter:
|
||||
startup: Type[:class:`cpl.core.application.startup_abc.StartupABC`]
|
||||
Startup class to use
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build(self) -> ApplicationABC:
|
||||
r"""Creates custom application object
|
||||
|
||||
Returns:
|
||||
Object of :class:`cpl.core.application.application_abc.ApplicationABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def build_async(self) -> ApplicationABC:
|
||||
r"""Creates custom application object async
|
||||
|
||||
Returns:
|
||||
Object of :class:`cpl.core.application.application_abc.ApplicationABC`
|
||||
"""
|
||||
@@ -0,0 +1,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration import ConfigurationABC
|
||||
from cpl.core.dependency_injection import ServiceProviderABC
|
||||
|
||||
|
||||
class ApplicationExtensionABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration import ConfigurationABC
|
||||
from cpl.core.dependency_injection import ServiceProviderABC
|
||||
|
||||
|
||||
class AsyncApplicationExtensionABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
pass
|
||||
32
src/cpl-core/cpl/core/application/async_startup_abc.py
Normal file
32
src/cpl-core/cpl/core/application/async_startup_abc.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class AsyncStartupABC(ABC):
|
||||
r"""ABC for the startup class"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.core.dependency_injection.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
@@ -0,0 +1,31 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class AsyncStartupExtensionABC(ABC):
|
||||
r"""ABC for startup extension classes"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.core.dependency_injection.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
32
src/cpl-core/cpl/core/application/startup_abc.py
Normal file
32
src/cpl-core/cpl/core/application/startup_abc.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class StartupABC(ABC):
|
||||
r"""ABC for the startup class"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.core.dependency_injection.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
31
src/cpl-core/cpl/core/application/startup_extension_abc.py
Normal file
31
src/cpl-core/cpl/core/application/startup_extension_abc.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class StartupExtensionABC(ABC):
|
||||
r"""ABC for startup extension classes"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.core.dependency_injection.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
12
src/cpl-core/cpl/core/configuration/__init__.py
Normal file
12
src/cpl-core/cpl/core/configuration/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from .argument_abc import ArgumentABC
|
||||
from .argument_builder import ArgumentBuilder
|
||||
from .argument_executable_abc import ArgumentExecutableABC
|
||||
from .argument_type_enum import ArgumentTypeEnum
|
||||
from .configuration import Configuration
|
||||
from .configuration_abc import ConfigurationABC
|
||||
from .configuration_model_abc import ConfigurationModelABC
|
||||
from .configuration_variable_name_enum import ConfigurationVariableNameEnum
|
||||
from .executable_argument import ExecutableArgument
|
||||
from .flag_argument import FlagArgument
|
||||
from .validator_abc import ValidatorABC
|
||||
from .variable_argument import VariableArgument
|
||||
64
src/cpl-core/cpl/core/configuration/argument_abc.py
Normal file
64
src/cpl-core/cpl/core/configuration/argument_abc.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
|
||||
|
||||
|
||||
class ArgumentABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
name: str,
|
||||
aliases: list[str],
|
||||
prevent_next_executable: bool = False,
|
||||
console_arguments: list["ArgumentABC"] = None,
|
||||
):
|
||||
r"""Representation of an console argument
|
||||
|
||||
Parameter:
|
||||
token: :class:`str`
|
||||
name: :class:`str`
|
||||
aliases: list[:class:`str`]
|
||||
console_arguments: List[:class:`cpl.core.configuration.console_argument.ConsoleArgument`]
|
||||
"""
|
||||
self._token = token
|
||||
self._name = name
|
||||
self._aliases = aliases
|
||||
self._prevent_next_executable = prevent_next_executable
|
||||
self._console_arguments = console_arguments if console_arguments is not None else []
|
||||
|
||||
@property
|
||||
def token(self) -> str:
|
||||
return self._token
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def aliases(self) -> list[str]:
|
||||
return self._aliases
|
||||
|
||||
@property
|
||||
def prevent_next_executable(self) -> bool:
|
||||
return self._prevent_next_executable
|
||||
|
||||
@property
|
||||
def console_arguments(self) -> list["ArgumentABC"]:
|
||||
return self._console_arguments
|
||||
|
||||
def add_console_argument(self, arg_type: ArgumentTypeEnum, *args, **kwargs) -> "ArgumentABC":
|
||||
r"""Creates and adds a console argument to known console arguments
|
||||
|
||||
Parameter:
|
||||
arg_type: :class:`str`
|
||||
Specifies the specific type of the argument
|
||||
|
||||
Returns:
|
||||
self :class:`cpl.core.configuration.console_argument.ConsoleArgument` not created argument!
|
||||
"""
|
||||
from cpl.core.configuration.argument_builder import ArgumentBuilder
|
||||
|
||||
argument = ArgumentBuilder.build_argument(arg_type, *args, *kwargs)
|
||||
self._console_arguments.append(argument)
|
||||
return self
|
||||
30
src/cpl-core/cpl/core/configuration/argument_builder.py
Normal file
30
src/cpl-core/cpl/core/configuration/argument_builder.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import Union
|
||||
|
||||
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
|
||||
from cpl.core.configuration.executable_argument import ExecutableArgument
|
||||
from cpl.core.configuration.flag_argument import FlagArgument
|
||||
from cpl.core.configuration.variable_argument import VariableArgument
|
||||
from cpl.core.console import Console
|
||||
|
||||
|
||||
class ArgumentBuilder:
|
||||
@staticmethod
|
||||
def build_argument(
|
||||
arg_type: ArgumentTypeEnum, *args, **kwargs
|
||||
) -> Union[ExecutableArgument, FlagArgument, VariableArgument]:
|
||||
argument = None
|
||||
try:
|
||||
match arg_type:
|
||||
case ArgumentTypeEnum.Flag:
|
||||
argument = FlagArgument(*args, **kwargs)
|
||||
case ArgumentTypeEnum.Executable:
|
||||
argument = ExecutableArgument(*args, **kwargs)
|
||||
case ArgumentTypeEnum.Variable:
|
||||
argument = VariableArgument(*args, **kwargs)
|
||||
case _:
|
||||
Console.error("Invalid argument type")
|
||||
Console.close()
|
||||
except TypeError as e:
|
||||
Console.error(str(e))
|
||||
Console.close()
|
||||
return argument
|
||||
@@ -0,0 +1,11 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ArgumentExecutableABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run(self, args: list[str]):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ArgumentTypeEnum(Enum):
|
||||
Flag = 0
|
||||
Executable = 1
|
||||
Variable = 3
|
||||
358
src/cpl-core/cpl/core/configuration/configuration.py
Normal file
358
src/cpl-core/cpl/core/configuration/configuration.py
Normal file
@@ -0,0 +1,358 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
from typing import Union, Optional
|
||||
|
||||
from cpl.core.configuration.argument_abc import ArgumentABC
|
||||
from cpl.core.configuration.argument_builder import ArgumentBuilder
|
||||
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.configuration.configuration_variable_name_enum import (
|
||||
ConfigurationVariableNameEnum,
|
||||
)
|
||||
from cpl.core.configuration.executable_argument import ExecutableArgument
|
||||
from cpl.core.configuration.flag_argument import FlagArgument
|
||||
from cpl.core.configuration.variable_argument import VariableArgument
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.environment.application_environment import ApplicationEnvironment
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.core.environment.environment_name_enum import EnvironmentNameEnum
|
||||
from cpl.core.typing import T, R
|
||||
from cpl.core.utils.json_processor import JSONProcessor
|
||||
|
||||
|
||||
class Configuration(ConfigurationABC):
|
||||
def __init__(self):
|
||||
r"""Representation of configuration"""
|
||||
ConfigurationABC.__init__(self)
|
||||
|
||||
self._application_environment = ApplicationEnvironment()
|
||||
self._config: dict[Union[type, str], Union[ConfigurationModelABC, str]] = {}
|
||||
|
||||
self._argument_types: list[ArgumentABC] = []
|
||||
self._additional_arguments: list[str] = []
|
||||
|
||||
self._argument_error_function: Optional[Callable] = None
|
||||
|
||||
self._handled_args = []
|
||||
|
||||
@property
|
||||
def environment(self) -> ApplicationEnvironmentABC:
|
||||
return self._application_environment
|
||||
|
||||
@property
|
||||
def additional_arguments(self) -> list[str]:
|
||||
return self._additional_arguments
|
||||
|
||||
@property
|
||||
def argument_error_function(self) -> Optional[Callable]:
|
||||
return self._argument_error_function
|
||||
|
||||
@argument_error_function.setter
|
||||
def argument_error_function(self, argument_error_function: Callable):
|
||||
self._argument_error_function = argument_error_function
|
||||
|
||||
@property
|
||||
def arguments(self) -> list[ArgumentABC]:
|
||||
return self._argument_types
|
||||
|
||||
@staticmethod
|
||||
def _print_info(message: str):
|
||||
r"""Prints an info message
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Info name
|
||||
message: :class:`str`
|
||||
Info message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.green)
|
||||
Console.write_line(f"[CONF] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
@staticmethod
|
||||
def _print_warn(message: str):
|
||||
r"""Prints a warning
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Warning name
|
||||
message: :class:`str`
|
||||
Warning message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.yellow)
|
||||
Console.write_line(f"[CONF] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
@staticmethod
|
||||
def _print_error(message: str):
|
||||
r"""Prints an error
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Error name
|
||||
message: :class:`str`
|
||||
Error message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.red)
|
||||
Console.write_line(f"[CONF] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
def _set_variable(self, name: str, value: any):
|
||||
r"""Sets variable to given value
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Name of the variable
|
||||
value: :class:`any`
|
||||
Value of the variable
|
||||
"""
|
||||
if name == ConfigurationVariableNameEnum.environment.value:
|
||||
self._application_environment.environment_name = EnvironmentNameEnum(value)
|
||||
|
||||
elif name == ConfigurationVariableNameEnum.name.value:
|
||||
self._application_environment.application_name = value
|
||||
|
||||
elif name == ConfigurationVariableNameEnum.customer.value:
|
||||
self._application_environment.customer = value
|
||||
|
||||
else:
|
||||
self._config[name] = value
|
||||
|
||||
def _load_json_file(self, file: str, output: bool) -> dict:
|
||||
r"""Reads the json file
|
||||
|
||||
Parameter:
|
||||
file: :class:`str`
|
||||
Name of the file
|
||||
output: :class:`bool`
|
||||
Specifies whether an output should take place
|
||||
|
||||
Returns:
|
||||
Object of :class:`dict`
|
||||
"""
|
||||
try:
|
||||
# open config file, create if not exists
|
||||
with open(file, encoding="utf-8") as cfg:
|
||||
# load json
|
||||
json_cfg = json.load(cfg)
|
||||
if output:
|
||||
self._print_info(f"Loaded config file: {file}")
|
||||
|
||||
return json_cfg
|
||||
except Exception as e:
|
||||
self._print_error(f"Cannot load config file: {file}! -> {e}")
|
||||
return {}
|
||||
|
||||
def _parse_arguments(
|
||||
self,
|
||||
executables: list[ArgumentABC],
|
||||
arg_list: list[str],
|
||||
args_types: list[ArgumentABC],
|
||||
):
|
||||
for i in range(0, len(arg_list)):
|
||||
arg_str = arg_list[i]
|
||||
for n in range(0, len(args_types)):
|
||||
arg = args_types[n]
|
||||
arg_str_without_token = arg_str
|
||||
if arg.token != "" and arg.token in arg_str:
|
||||
arg_str_without_token = arg_str.split(arg.token)[1]
|
||||
|
||||
# executable
|
||||
if isinstance(arg, ExecutableArgument):
|
||||
if (
|
||||
arg_str.startswith(arg.token)
|
||||
and arg_str_without_token == arg.name
|
||||
or arg_str_without_token in arg.aliases
|
||||
):
|
||||
executables.append(arg)
|
||||
self._handled_args.append(arg_str)
|
||||
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
|
||||
|
||||
# variables
|
||||
elif isinstance(arg, VariableArgument):
|
||||
arg_str_without_value = arg_str_without_token
|
||||
if arg.value_token in arg_str_without_value:
|
||||
arg_str_without_value = arg_str_without_token.split(arg.value_token)[0]
|
||||
|
||||
if (
|
||||
arg_str.startswith(arg.token)
|
||||
and arg_str_without_value == arg.name
|
||||
or arg_str_without_value in arg.aliases
|
||||
):
|
||||
if arg.value_token != " ":
|
||||
value = arg_str_without_token.split(arg.value_token)[1]
|
||||
else:
|
||||
value = arg_list[i + 1]
|
||||
self._set_variable(arg.name, value)
|
||||
self._handled_args.append(arg_str)
|
||||
self._handled_args.append(value)
|
||||
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
|
||||
|
||||
# flags
|
||||
elif isinstance(arg, FlagArgument):
|
||||
if (
|
||||
arg_str.startswith(arg.token)
|
||||
and arg_str_without_token == arg.name
|
||||
or arg_str_without_token in arg.aliases
|
||||
):
|
||||
if arg_str in self._additional_arguments:
|
||||
self._additional_arguments.remove(arg_str)
|
||||
self._additional_arguments.append(arg.name)
|
||||
self._handled_args.append(arg_str)
|
||||
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
|
||||
|
||||
# add left over values to args
|
||||
if arg_str not in self._additional_arguments and arg_str not in self._handled_args:
|
||||
self._additional_arguments.append(arg_str)
|
||||
|
||||
def add_environment_variables(self, prefix: str):
|
||||
for env_var in os.environ.keys():
|
||||
if not env_var.startswith(prefix):
|
||||
continue
|
||||
|
||||
self._set_variable(env_var.replace(prefix, ""), os.environ[env_var])
|
||||
|
||||
def add_console_argument(self, argument: ArgumentABC):
|
||||
self._argument_types.append(argument)
|
||||
|
||||
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
|
||||
if os.path.isabs(name):
|
||||
file_path = name
|
||||
else:
|
||||
path_root = self._application_environment.working_directory
|
||||
if path is not None:
|
||||
path_root = path
|
||||
|
||||
if str(path_root).endswith("/") and not name.startswith("/"):
|
||||
file_path = f"{path_root}{name}"
|
||||
else:
|
||||
file_path = f"{path_root}/{name}"
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
if optional is not True:
|
||||
if output:
|
||||
self._print_error(f"File not found: {file_path}")
|
||||
|
||||
sys.exit()
|
||||
|
||||
if output:
|
||||
self._print_warn(__name__, f"Not Loaded config file: {file_path}")
|
||||
|
||||
return None
|
||||
|
||||
config_from_file = self._load_json_file(file_path, output)
|
||||
for sub in ConfigurationModelABC.__subclasses__():
|
||||
for key, value in config_from_file.items():
|
||||
if sub.__name__ != key and sub.__name__.replace("Settings", "") != key:
|
||||
continue
|
||||
|
||||
configuration = sub()
|
||||
from_dict = getattr(configuration, "from_dict", None)
|
||||
|
||||
if from_dict is not None and not hasattr(from_dict, "is_base_func"):
|
||||
Console.set_foreground_color(ForegroundColorEnum.yellow)
|
||||
Console.write_line(
|
||||
f"{sub.__name__}.from_dict is deprecated. Instead, set attributes as typed arguments in __init__. They can be None by default!"
|
||||
)
|
||||
Console.color_reset()
|
||||
configuration.from_dict(value)
|
||||
else:
|
||||
configuration = JSONProcessor.process(sub, value)
|
||||
|
||||
self.add_configuration(sub, configuration)
|
||||
|
||||
def add_configuration(self, key_type: T, value: any):
|
||||
self._config[key_type] = value
|
||||
|
||||
def create_console_argument(
|
||||
self,
|
||||
arg_type: ArgumentTypeEnum,
|
||||
token: str,
|
||||
name: str,
|
||||
aliases: list[str],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> ArgumentABC:
|
||||
argument = ArgumentBuilder.build_argument(arg_type, token, name, aliases, *args, **kwargs)
|
||||
self._argument_types.append(argument)
|
||||
return argument
|
||||
|
||||
def for_each_argument(self, call: Callable):
|
||||
for arg in self._argument_types:
|
||||
call(arg)
|
||||
|
||||
def get_configuration(self, search_type: T) -> Optional[R]:
|
||||
if type(search_type) is str:
|
||||
if search_type == ConfigurationVariableNameEnum.environment.value:
|
||||
return self._application_environment.environment_name
|
||||
|
||||
elif search_type == ConfigurationVariableNameEnum.name.value:
|
||||
return self._application_environment.application_name
|
||||
|
||||
elif search_type == ConfigurationVariableNameEnum.customer.value:
|
||||
return self._application_environment.customer
|
||||
|
||||
if search_type not in self._config:
|
||||
return None
|
||||
|
||||
for config_model in self._config:
|
||||
if config_model == search_type:
|
||||
return self._config[config_model]
|
||||
|
||||
def parse_console_arguments(self, services: ServiceProviderABC, error: bool = None) -> bool:
|
||||
# sets environment variables as possible arguments as: --VAR=VALUE
|
||||
for arg_name in ConfigurationVariableNameEnum.to_list():
|
||||
self.add_console_argument(VariableArgument("--", str(arg_name).upper(), [str(arg_name).lower()], "="))
|
||||
|
||||
success = False
|
||||
try:
|
||||
arg_list = sys.argv[1:]
|
||||
executables: list[ExecutableArgument] = []
|
||||
self._parse_arguments(executables, arg_list, self._argument_types)
|
||||
except Exception:
|
||||
Console.error("An error occurred while parsing arguments.", traceback.format_exc())
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
prevent = False
|
||||
for exe in executables:
|
||||
if prevent:
|
||||
continue
|
||||
|
||||
if exe.validators is not None:
|
||||
abort = False
|
||||
for validator_type in exe.validators:
|
||||
validator = services.get_service(validator_type)
|
||||
result = validator.validate()
|
||||
abort = not result
|
||||
if abort:
|
||||
break
|
||||
|
||||
if abort:
|
||||
sys.exit()
|
||||
|
||||
cmd = services.get_service(exe.executable_type)
|
||||
self._handle_pre_or_post_executables(True, exe, services)
|
||||
self._set_variable("ACTIVE_EXECUTABLE", exe.name)
|
||||
args = self.get_configuration("ARGS")
|
||||
if args is not None:
|
||||
for arg in args.split(" "):
|
||||
if arg == "":
|
||||
continue
|
||||
self._additional_arguments.append(arg)
|
||||
|
||||
cmd.run(self._additional_arguments)
|
||||
self._handle_pre_or_post_executables(False, exe, services)
|
||||
prevent = exe.prevent_next_executable
|
||||
success = True
|
||||
except Exception:
|
||||
Console.error("An error occurred while executing arguments.", traceback.format_exc())
|
||||
sys.exit()
|
||||
|
||||
return success
|
||||
140
src/cpl-core/cpl/core/configuration/configuration_abc.py
Normal file
140
src/cpl-core/cpl/core/configuration/configuration_abc.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.argument_abc import ArgumentABC
|
||||
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.core.typing import T, R
|
||||
|
||||
|
||||
class ConfigurationABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
r"""ABC for the :class:`cpl.core.configuration.configuration.Configuration`"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def environment(self) -> ApplicationEnvironmentABC:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def additional_arguments(self) -> list[str]:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def argument_error_function(self) -> Optional[Callable]:
|
||||
pass
|
||||
|
||||
@argument_error_function.setter
|
||||
@abstractmethod
|
||||
def argument_error_function(self, argument_error_function: Callable):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def arguments(self) -> list[ArgumentABC]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_environment_variables(self, prefix: str):
|
||||
r"""Reads the environment variables
|
||||
|
||||
Parameter:
|
||||
prefix: :class:`str`
|
||||
Prefix of the variables
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_console_argument(self, argument: ArgumentABC):
|
||||
r"""Adds console argument to known console arguments
|
||||
|
||||
Parameter:
|
||||
argument: :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
|
||||
Specifies the console argument
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
|
||||
r"""Reads and saves settings from given json file
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Name of the file
|
||||
optional: :class:`str`
|
||||
Specifies whether an error should occur if the file was not found
|
||||
output: :class:`bool`
|
||||
Specifies whether an output should take place
|
||||
path: :class:`str`
|
||||
Path in which the file should be stored
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_configuration(self, key_type: T, value: any):
|
||||
r"""Add configuration object
|
||||
|
||||
Parameter:
|
||||
key_type: :class:`cpl.core.type.T`
|
||||
Type of the value
|
||||
value: any
|
||||
Object of the value
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_console_argument(
|
||||
self, arg_type: ArgumentTypeEnum, token: str, name: str, aliases: list[str], *args, **kwargs
|
||||
) -> ArgumentABC:
|
||||
r"""Creates and adds a console argument to known console arguments
|
||||
|
||||
Parameter:
|
||||
token: :class:`str`
|
||||
Specifies optional beginning of argument
|
||||
name :class:`str`
|
||||
Specifies name of argument
|
||||
aliases list[:class:`str`]
|
||||
Specifies possible aliases of name
|
||||
value_token :class:`str`
|
||||
Specifies were the value begins
|
||||
is_value_token_optional :class:`bool`
|
||||
Specifies if values are optional
|
||||
runnable: :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
|
||||
Specifies class to run when called if value is not None
|
||||
|
||||
Returns:
|
||||
Object of :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def for_each_argument(self, call: Callable):
|
||||
r"""Iterates through all arguments and calls the call function
|
||||
|
||||
Parameter:
|
||||
call: :class:`Callable`
|
||||
Call for each argument
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_configuration(self, search_type: T) -> Optional[R]:
|
||||
r"""Returns value from configuration by given type
|
||||
|
||||
Parameter:
|
||||
search_type: :class:`cpl.core.type.T`
|
||||
Type to search for
|
||||
|
||||
Returns:
|
||||
Object of Union[:class:`str`, :class:`cpl.core.configuration.configuration_model_abc.ConfigurationModelABC`]
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def parse_console_arguments(self, services: "ServiceProviderABC", error: bool = None) -> bool:
|
||||
r"""Reads the console arguments
|
||||
|
||||
Parameter:
|
||||
error: :class:`bool`
|
||||
Defines is invalid argument error will be shown or not
|
||||
|
||||
Returns:
|
||||
Bool to specify if executables were executed or not.
|
||||
"""
|
||||
@@ -0,0 +1,21 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
def base_func(method):
|
||||
method.is_base_func = True
|
||||
return method
|
||||
|
||||
|
||||
class ConfigurationModelABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
r"""ABC for settings representation"""
|
||||
|
||||
@base_func
|
||||
def from_dict(self, settings: dict):
|
||||
r"""DEPRECATED: Set attributes as typed arguments in __init__ instead. See https://docs.sh-edraft.de/cpl/deprecated.html#ConfigurationModelABC-from_dict-method for further information
|
||||
Converts attributes to dict
|
||||
|
||||
Parameter:
|
||||
settings: :class:`dict`
|
||||
"""
|
||||
@@ -2,10 +2,9 @@ from enum import Enum
|
||||
|
||||
|
||||
class ConfigurationVariableNameEnum(Enum):
|
||||
|
||||
environment = 'ENVIRONMENT'
|
||||
name = 'NAME'
|
||||
customer = 'CUSTOMER'
|
||||
environment = "ENVIRONMENT"
|
||||
name = "NAME"
|
||||
customer = "CUSTOMER"
|
||||
|
||||
@staticmethod
|
||||
def to_list():
|
||||
40
src/cpl-core/cpl/core/configuration/executable_argument.py
Normal file
40
src/cpl-core/cpl/core/configuration/executable_argument.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Type, Optional
|
||||
|
||||
from cpl.core.configuration.argument_executable_abc import ArgumentExecutableABC
|
||||
from cpl.core.configuration.argument_abc import ArgumentABC
|
||||
from cpl.core.configuration.validator_abc import ValidatorABC
|
||||
|
||||
|
||||
class ExecutableArgument(ArgumentABC):
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
name: str,
|
||||
aliases: list[str],
|
||||
executable: Type[ArgumentExecutableABC],
|
||||
prevent_next_executable: bool = False,
|
||||
validators: list[Type[ValidatorABC]] = None,
|
||||
console_arguments: list["ArgumentABC"] = None,
|
||||
):
|
||||
self._executable_type = executable
|
||||
self._validators = validators
|
||||
self._executable: Optional[ArgumentExecutableABC] = None
|
||||
|
||||
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)
|
||||
|
||||
@property
|
||||
def executable_type(self) -> type:
|
||||
return self._executable_type
|
||||
|
||||
def set_executable(self, executable: ArgumentExecutableABC):
|
||||
self._executable = executable
|
||||
|
||||
@property
|
||||
def validators(self) -> list[Type[ValidatorABC]]:
|
||||
return self._validators
|
||||
|
||||
def run(self, args: list[str]):
|
||||
r"""Executes runnable if exists"""
|
||||
if self._executable is None:
|
||||
return
|
||||
self._executable.execute(args)
|
||||
13
src/cpl-core/cpl/core/configuration/flag_argument.py
Normal file
13
src/cpl-core/cpl/core/configuration/flag_argument.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from cpl.core.configuration.argument_abc import ArgumentABC
|
||||
|
||||
|
||||
class FlagArgument(ArgumentABC):
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
name: str,
|
||||
aliases: list[str],
|
||||
prevent_next_executable: bool = False,
|
||||
console_arguments: list["ArgumentABC"] = None,
|
||||
):
|
||||
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)
|
||||
@@ -1,11 +1,11 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ServiceABC(ABC):
|
||||
|
||||
class ValidatorABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
"""
|
||||
ABC to represent a service
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def validate(self) -> bool:
|
||||
pass
|
||||
28
src/cpl-core/cpl/core/configuration/variable_argument.py
Normal file
28
src/cpl-core/cpl/core/configuration/variable_argument.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from cpl.core.configuration.argument_abc import ArgumentABC
|
||||
|
||||
|
||||
class VariableArgument(ArgumentABC):
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
name: str,
|
||||
aliases: list[str],
|
||||
value_token: str,
|
||||
prevent_next_executable: bool = False,
|
||||
console_arguments: list["ArgumentABC"] = None,
|
||||
):
|
||||
self._value_token = value_token
|
||||
self._value: str = ""
|
||||
|
||||
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)
|
||||
|
||||
@property
|
||||
def value_token(self) -> str:
|
||||
return self._value_token
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self._value
|
||||
|
||||
def set_value(self, value: str):
|
||||
self._value = value
|
||||
5
src/cpl-core/cpl/core/console/__init__.py
Normal file
5
src/cpl-core/cpl/core/console/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .background_color_enum import BackgroundColorEnum
|
||||
from .console import Console
|
||||
from .console_call import ConsoleCall
|
||||
from .foreground_color_enum import ForegroundColorEnum
|
||||
from .spinner_thread import SpinnerThread
|
||||
13
src/cpl-core/cpl/core/console/background_color_enum.py
Normal file
13
src/cpl-core/cpl/core/console/background_color_enum.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BackgroundColorEnum(Enum):
|
||||
default = "on_default"
|
||||
grey = "on_grey"
|
||||
red = "on_red"
|
||||
green = "on_green"
|
||||
yellow = "on_yellow"
|
||||
blue = "on_blue"
|
||||
magenta = "on_magenta"
|
||||
cyan = "on_cyan"
|
||||
white = "on_white"
|
||||
578
src/cpl-core/cpl/core/console/console.py
Normal file
578
src/cpl-core/cpl/core/console/console.py
Normal file
@@ -0,0 +1,578 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from typing import Union, Optional
|
||||
|
||||
from art import text2art
|
||||
import colorama
|
||||
from tabulate import tabulate
|
||||
from termcolor import colored
|
||||
|
||||
from cpl.core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.core.console.console_call import ConsoleCall
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.console.spinner_thread import SpinnerThread
|
||||
|
||||
|
||||
class Console:
|
||||
r"""Useful functions for handling with input and output"""
|
||||
|
||||
colorama.init()
|
||||
_is_first_write = True
|
||||
|
||||
_background_color: BackgroundColorEnum = BackgroundColorEnum.default
|
||||
_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
|
||||
_x: Optional[int] = None
|
||||
_y: Optional[int] = None
|
||||
_disabled: bool = False
|
||||
|
||||
_hold_back = False
|
||||
_hold_back_calls: list[ConsoleCall] = []
|
||||
|
||||
_select_menu_items: list[str] = []
|
||||
_is_first_select_menu_output = True
|
||||
_selected_menu_item_index: int = 0
|
||||
_selected_menu_item_char: str = ""
|
||||
_selected_menu_option_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
|
||||
_selected_menu_option_background_color: BackgroundColorEnum = BackgroundColorEnum.default
|
||||
_selected_menu_cursor_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
|
||||
_selected_menu_cursor_background_color: BackgroundColorEnum = BackgroundColorEnum.default
|
||||
|
||||
"""Properties"""
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def background_color(cls) -> str:
|
||||
return str(cls._background_color.value)
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def foreground_color(cls) -> str:
|
||||
return str(cls._foreground_color.value)
|
||||
|
||||
"""Settings"""
|
||||
|
||||
@classmethod
|
||||
def set_hold_back(cls, value: bool):
|
||||
cls._hold_back = value
|
||||
|
||||
@classmethod
|
||||
def set_background_color(cls, color: Union[BackgroundColorEnum, str]):
|
||||
r"""Sets the background color
|
||||
|
||||
Parameter:
|
||||
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
Background color of the console
|
||||
"""
|
||||
if type(color) is str:
|
||||
cls._background_color = BackgroundColorEnum[color]
|
||||
else:
|
||||
cls._background_color = color
|
||||
|
||||
@classmethod
|
||||
def set_foreground_color(cls, color: Union[ForegroundColorEnum, str]):
|
||||
r"""Sets the foreground color
|
||||
|
||||
Parameter:
|
||||
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
Foreground color of the console
|
||||
"""
|
||||
if type(color) is str:
|
||||
cls._foreground_color = ForegroundColorEnum[color]
|
||||
else:
|
||||
cls._foreground_color = color
|
||||
|
||||
@classmethod
|
||||
def reset_cursor_position(cls):
|
||||
r"""Resets cursor position"""
|
||||
cls._x = None
|
||||
cls._y = None
|
||||
|
||||
@classmethod
|
||||
def set_cursor_position(cls, x: int, y: int):
|
||||
r"""Sets cursor position
|
||||
|
||||
Parameter:
|
||||
x: :class:`int`
|
||||
X coordinate
|
||||
y: :class:`int`
|
||||
Y coordinate
|
||||
"""
|
||||
cls._x = x
|
||||
cls._y = y
|
||||
|
||||
"""Useful protected functions"""
|
||||
|
||||
@classmethod
|
||||
def _output(cls, string: str, x: int = None, y: int = None, end: str = None):
|
||||
r"""Prints given output with given format
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
Message to print
|
||||
x: :class:`int`
|
||||
X coordinate
|
||||
y: :class:`int`
|
||||
Y coordinate
|
||||
end: :class:`str`
|
||||
End character of the message (could be \n)
|
||||
"""
|
||||
if cls._is_first_write:
|
||||
cls._is_first_write = False
|
||||
|
||||
if end is None:
|
||||
end = "\n"
|
||||
|
||||
args = []
|
||||
colored_args = []
|
||||
if x is not None and y is not None:
|
||||
args.append(f"\033[{y};{x}H")
|
||||
elif cls._x is not None and cls._y is not None:
|
||||
args.append(f"\033[{cls._y};{cls._x}H")
|
||||
|
||||
colored_args.append(string)
|
||||
if (
|
||||
cls._foreground_color != ForegroundColorEnum.default
|
||||
and cls._background_color == BackgroundColorEnum.default
|
||||
):
|
||||
colored_args.append(cls._foreground_color.value)
|
||||
elif (
|
||||
cls._foreground_color == ForegroundColorEnum.default
|
||||
and cls._background_color != BackgroundColorEnum.default
|
||||
):
|
||||
colored_args.append(cls._background_color.value)
|
||||
elif (
|
||||
cls._foreground_color != ForegroundColorEnum.default
|
||||
and cls._background_color != BackgroundColorEnum.default
|
||||
):
|
||||
colored_args.append(cls._foreground_color.value)
|
||||
colored_args.append(cls._background_color.value)
|
||||
|
||||
args.append(colored(*colored_args))
|
||||
print(*args, end=end)
|
||||
|
||||
@classmethod
|
||||
def _show_select_menu(cls):
|
||||
r"""Shows the select menu"""
|
||||
if not cls._is_first_select_menu_output:
|
||||
for _ in range(0, len(cls._select_menu_items) + 1):
|
||||
sys.stdout.write("\x1b[1A\x1b[2K")
|
||||
else:
|
||||
cls._is_first_select_menu_output = False
|
||||
|
||||
for i in range(0, len(cls._select_menu_items)):
|
||||
Console.set_foreground_color(cls._selected_menu_cursor_foreground_color)
|
||||
Console.set_background_color(cls._selected_menu_cursor_background_color)
|
||||
placeholder = ""
|
||||
for _ in cls._selected_menu_item_char:
|
||||
placeholder += " "
|
||||
|
||||
Console.write_line(
|
||||
f"{cls._selected_menu_item_char if cls._selected_menu_item_index == i else placeholder} "
|
||||
)
|
||||
Console.set_foreground_color(cls._selected_menu_option_foreground_color)
|
||||
Console.set_background_color(cls._selected_menu_option_background_color)
|
||||
Console.write(f"{cls._select_menu_items[i]}")
|
||||
|
||||
Console.write_line()
|
||||
|
||||
@classmethod
|
||||
def _select_menu_key_press(cls, key):
|
||||
r"""Event function when key press is detected
|
||||
|
||||
Parameter:
|
||||
key: :class:`pynput.keyboard.Key`
|
||||
Pressed key
|
||||
"""
|
||||
from pynput.keyboard import Key
|
||||
|
||||
if key == Key.down:
|
||||
if cls._selected_menu_item_index == len(cls._select_menu_items) - 1:
|
||||
return
|
||||
cls._selected_menu_item_index += 1
|
||||
cls._show_select_menu()
|
||||
|
||||
elif key == Key.up:
|
||||
if cls._selected_menu_item_index == 0:
|
||||
return
|
||||
cls._selected_menu_item_index -= 1
|
||||
cls._show_select_menu()
|
||||
|
||||
elif key == Key.enter:
|
||||
return False
|
||||
|
||||
""" Useful public functions"""
|
||||
|
||||
@classmethod
|
||||
def banner(cls, string: str):
|
||||
r"""Prints the string as a banner
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
Message to print as banner
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.banner, string))
|
||||
return
|
||||
|
||||
cls.write_line(text2art(string))
|
||||
|
||||
@classmethod
|
||||
def color_reset(cls):
|
||||
r"""Resets the color settings"""
|
||||
cls._background_color = BackgroundColorEnum.default
|
||||
cls._foreground_color = ForegroundColorEnum.default
|
||||
|
||||
@classmethod
|
||||
def clear(cls):
|
||||
r"""Clears the console"""
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.clear))
|
||||
return
|
||||
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
|
||||
@classmethod
|
||||
def close(cls):
|
||||
r"""Closes the application"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.close))
|
||||
return
|
||||
|
||||
Console.color_reset()
|
||||
Console.write("\n\n\nPress any key to continue...")
|
||||
Console.read()
|
||||
sys.exit()
|
||||
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
r"""Disables console interaction"""
|
||||
cls._disabled = True
|
||||
|
||||
@classmethod
|
||||
def error(cls, string: str, tb: str = None):
|
||||
r"""Prints an error with traceback
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
Error message
|
||||
tb: :class:`str`
|
||||
Error traceback
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.error, string, tb))
|
||||
return
|
||||
|
||||
cls.set_foreground_color("red")
|
||||
if tb is not None:
|
||||
cls.write_line(f"{string} -> {tb}")
|
||||
else:
|
||||
cls.write_line(string)
|
||||
cls.set_foreground_color("default")
|
||||
|
||||
@classmethod
|
||||
def enable(cls):
|
||||
r"""Enables console interaction"""
|
||||
cls._disabled = False
|
||||
|
||||
@classmethod
|
||||
def read(cls, output: str = None) -> str:
|
||||
r"""Reads in line
|
||||
|
||||
Parameter:
|
||||
output: :class:`str`
|
||||
String to print before input
|
||||
|
||||
Returns:
|
||||
input()
|
||||
"""
|
||||
if output is not None and not cls._hold_back:
|
||||
cls.write_line(output)
|
||||
|
||||
return input()
|
||||
|
||||
@classmethod
|
||||
def read_line(cls, output: str = None) -> str:
|
||||
r"""Reads in next line
|
||||
|
||||
Parameter:
|
||||
output: :class:`str`
|
||||
String to print before input
|
||||
|
||||
Returns:
|
||||
input()
|
||||
"""
|
||||
if cls._disabled and not cls._hold_back:
|
||||
return ""
|
||||
|
||||
if output is not None:
|
||||
cls.write_line(output)
|
||||
|
||||
cls._output("\n", end="")
|
||||
|
||||
return input()
|
||||
|
||||
@classmethod
|
||||
def table(cls, header: list[str], values: list[list[str]]):
|
||||
r"""Prints a table with header and values
|
||||
|
||||
Parameter:
|
||||
header: List[:class:`str`]
|
||||
Header of the table
|
||||
values: List[List[:class:`str`]]
|
||||
Values of the table
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.table, header, values))
|
||||
return
|
||||
|
||||
table = tabulate(values, headers=header)
|
||||
|
||||
Console.write_line(table)
|
||||
Console.write("\n")
|
||||
|
||||
@classmethod
|
||||
def select(
|
||||
cls,
|
||||
char: str,
|
||||
message: str,
|
||||
options: list[str],
|
||||
header_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
|
||||
header_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
|
||||
option_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
|
||||
option_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
|
||||
cursor_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
|
||||
cursor_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
|
||||
) -> str:
|
||||
r"""Prints select menu
|
||||
|
||||
Parameter:
|
||||
char: :class:`str`
|
||||
Character to show which element is selected
|
||||
message: :class:`str`
|
||||
Message or header of the selection
|
||||
options: List[:class:`str`]
|
||||
Selectable options
|
||||
header_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the header
|
||||
header_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the header
|
||||
option_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the options
|
||||
option_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the options
|
||||
cursor_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the cursor
|
||||
cursor_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the cursor
|
||||
|
||||
Returns:
|
||||
Selected option as :class:`str`
|
||||
"""
|
||||
cls._selected_menu_item_char = char
|
||||
cls.options = options
|
||||
cls._select_menu_items = cls.options
|
||||
|
||||
if option_foreground_color is not None:
|
||||
cls._selected_menu_option_foreground_color = option_foreground_color
|
||||
if option_background_color is not None:
|
||||
cls._selected_menu_option_background_color = option_background_color
|
||||
|
||||
if cursor_foreground_color is not None:
|
||||
cls._selected_menu_cursor_foreground_color = cursor_foreground_color
|
||||
if cursor_background_color is not None:
|
||||
cls._selected_menu_cursor_background_color = cursor_background_color
|
||||
|
||||
Console.set_foreground_color(header_foreground_color)
|
||||
Console.set_background_color(header_background_color)
|
||||
Console.write_line(message, "\n")
|
||||
cls._show_select_menu()
|
||||
|
||||
from pynput import keyboard
|
||||
|
||||
with keyboard.Listener(on_press=cls._select_menu_key_press, suppress=False) as listener:
|
||||
listener.join()
|
||||
|
||||
Console.color_reset()
|
||||
return cls._select_menu_items[cls._selected_menu_item_index]
|
||||
|
||||
@classmethod
|
||||
def spinner(
|
||||
cls,
|
||||
message: str,
|
||||
call: Callable,
|
||||
*args,
|
||||
text_foreground_color: Union[str, ForegroundColorEnum] = None,
|
||||
spinner_foreground_color: Union[str, ForegroundColorEnum] = None,
|
||||
text_background_color: Union[str, BackgroundColorEnum] = None,
|
||||
spinner_background_color: Union[str, BackgroundColorEnum] = None,
|
||||
**kwargs,
|
||||
) -> any:
|
||||
r"""Shows spinner and calls given function, when function has ended the spinner stops
|
||||
|
||||
Parameter:
|
||||
message: :class:`str`
|
||||
Message of the spinner
|
||||
call: :class:`Callable`
|
||||
Function to call
|
||||
args: :class:`list`
|
||||
Arguments of the function
|
||||
text_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the text
|
||||
spinner_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the spinner
|
||||
text_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the text
|
||||
spinner_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the spinner
|
||||
kwargs: :class:`dict`
|
||||
Keyword arguments of the call
|
||||
|
||||
Returns:
|
||||
Return value of call
|
||||
"""
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.spinner, message, call, *args))
|
||||
return
|
||||
|
||||
if text_foreground_color is not None:
|
||||
cls.set_foreground_color(text_foreground_color)
|
||||
|
||||
if text_background_color is not None:
|
||||
cls.set_background_color(text_background_color)
|
||||
|
||||
if type(spinner_foreground_color) is str:
|
||||
spinner_foreground_color = ForegroundColorEnum[spinner_foreground_color]
|
||||
|
||||
if type(spinner_background_color) is str:
|
||||
spinner_background_color = BackgroundColorEnum[spinner_background_color]
|
||||
|
||||
cls.write_line(message)
|
||||
cls.set_hold_back(True)
|
||||
spinner = None
|
||||
if not cls._disabled:
|
||||
spinner = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color)
|
||||
spinner.start()
|
||||
|
||||
return_value = None
|
||||
try:
|
||||
return_value = call(*args, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
if spinner is not None:
|
||||
spinner.exit()
|
||||
cls.close()
|
||||
|
||||
if spinner is not None:
|
||||
spinner.stop_spinning()
|
||||
cls.set_hold_back(False)
|
||||
|
||||
cls.set_foreground_color(ForegroundColorEnum.default)
|
||||
cls.set_background_color(BackgroundColorEnum.default)
|
||||
|
||||
for call in cls._hold_back_calls:
|
||||
call.function(*call.args)
|
||||
|
||||
cls._hold_back_calls = []
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
return return_value
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args, end=""):
|
||||
r"""Prints in active line
|
||||
|
||||
Parameter:
|
||||
args: :class:`list`
|
||||
Elements to print
|
||||
end: :class:`str`
|
||||
Last character to print
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.write, *args))
|
||||
return
|
||||
|
||||
string = " ".join(map(str, args))
|
||||
cls._output(string, end=end)
|
||||
|
||||
@classmethod
|
||||
def write_at(cls, x: int, y: int, *args):
|
||||
r"""Prints at given position
|
||||
|
||||
Parameter:
|
||||
x: :class:`int`
|
||||
X coordinate
|
||||
y: :class:`int`
|
||||
Y coordinate
|
||||
args: :class:`list`
|
||||
Elements to print
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.write_at, x, y, *args))
|
||||
return
|
||||
|
||||
string = " ".join(map(str, args))
|
||||
cls._output(string, x, y, end="")
|
||||
|
||||
@classmethod
|
||||
def write_line(cls, *args):
|
||||
r"""Prints to new line
|
||||
|
||||
Parameter:
|
||||
args: :class:`list`
|
||||
Elements to print
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.write_line, *args))
|
||||
return
|
||||
|
||||
string = " ".join(map(str, args))
|
||||
if not cls._is_first_write:
|
||||
cls._output("")
|
||||
cls._output(string, end="")
|
||||
|
||||
@classmethod
|
||||
def write_line_at(cls, x: int, y: int, *args):
|
||||
r"""Prints new line at given position
|
||||
|
||||
Parameter:
|
||||
x: :class:`int`
|
||||
X coordinate
|
||||
y: :class:`int`
|
||||
Y coordinate
|
||||
args: :class:`list`
|
||||
Elements to print
|
||||
"""
|
||||
if cls._disabled:
|
||||
return
|
||||
|
||||
if cls._hold_back:
|
||||
cls._hold_back_calls.append(ConsoleCall(cls.write_line_at, x, y, *args))
|
||||
return
|
||||
|
||||
string = " ".join(map(str, args))
|
||||
if not cls._is_first_write:
|
||||
cls._output("", end="")
|
||||
cls._output(string, x, y, end="")
|
||||
@@ -1,14 +1,17 @@
|
||||
from collections import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
class ConsoleCall:
|
||||
r"""Represents a console call, for hold back when spinner is active
|
||||
|
||||
Parameter:
|
||||
function: :class:`Callable`
|
||||
Function to call
|
||||
args: :class:`list`
|
||||
List of arguments
|
||||
"""
|
||||
|
||||
def __init__(self, function: Callable, *args):
|
||||
"""
|
||||
Represents a console call, for hold back when spinner is active
|
||||
:param function:
|
||||
:param args:
|
||||
"""
|
||||
self._func = function
|
||||
self._args = args
|
||||
|
||||
13
src/cpl-core/cpl/core/console/foreground_color_enum.py
Normal file
13
src/cpl-core/cpl/core/console/foreground_color_enum.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ForegroundColorEnum(Enum):
|
||||
default = "default"
|
||||
grey = "grey"
|
||||
red = "red"
|
||||
green = "green"
|
||||
yellow = "yellow"
|
||||
blue = "blue"
|
||||
magenta = "magenta"
|
||||
cyan = "cyan"
|
||||
white = "white"
|
||||
@@ -5,19 +5,23 @@ import time
|
||||
|
||||
from termcolor import colored
|
||||
|
||||
from cpl.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
|
||||
|
||||
class SpinnerThread(threading.Thread):
|
||||
r"""Thread to show spinner in terminal
|
||||
|
||||
Parameter:
|
||||
msg_len: :class:`int`
|
||||
Length of the message
|
||||
foreground_color: :class:`cpl.core.console.foreground_color.ForegroundColorEnum`
|
||||
Foreground color of the spinner
|
||||
background_color: :class:`cpl.core.console.background_color.BackgroundColorEnum`
|
||||
Background color of the spinner
|
||||
"""
|
||||
|
||||
def __init__(self, msg_len: int, foreground_color: ForegroundColorEnum, background_color: BackgroundColorEnum):
|
||||
"""
|
||||
Thread to show spinner in terminal
|
||||
:param msg_len:
|
||||
:param foreground_color:
|
||||
:param background_color:
|
||||
"""
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self._msg_len = msg_len
|
||||
@@ -29,19 +33,13 @@ class SpinnerThread(threading.Thread):
|
||||
|
||||
@staticmethod
|
||||
def _spinner():
|
||||
"""
|
||||
Selects active spinner char
|
||||
:return:
|
||||
"""
|
||||
r"""Selects active spinner char"""
|
||||
while True:
|
||||
for cursor in '|/-\\':
|
||||
for cursor in "|/-\\":
|
||||
yield cursor
|
||||
|
||||
def _get_color_args(self) -> list[str]:
|
||||
"""
|
||||
Creates color arguments
|
||||
:return:
|
||||
"""
|
||||
r"""Creates color arguments"""
|
||||
color_args = []
|
||||
if self._foreground_color is not None:
|
||||
color_args.append(str(self._foreground_color.value))
|
||||
@@ -52,56 +50,47 @@ class SpinnerThread(threading.Thread):
|
||||
return color_args
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Entry point of thread, shows the spinner
|
||||
:return:
|
||||
"""
|
||||
r"""Entry point of thread, shows the spinner"""
|
||||
columns = 0
|
||||
if sys.platform == 'win32':
|
||||
if sys.platform == "win32":
|
||||
columns = os.get_terminal_size().columns
|
||||
else:
|
||||
term_rows, term_columns = os.popen('stty size', 'r').read().split()
|
||||
term_rows, term_columns = os.popen("stty size", "r").read().split()
|
||||
columns = int(term_columns)
|
||||
|
||||
end_msg = 'done'
|
||||
end_msg = "done"
|
||||
end_msg_pos = columns - self._msg_len - len(end_msg)
|
||||
if end_msg_pos > 0:
|
||||
print(f'{"" : >{end_msg_pos}}', end='')
|
||||
print(f'{"" : >{end_msg_pos}}', end="")
|
||||
else:
|
||||
print('', end='')
|
||||
print("", end="")
|
||||
|
||||
first = True
|
||||
spinner = self._spinner()
|
||||
while self._is_spinning:
|
||||
if first:
|
||||
first = False
|
||||
print(colored(f'{next(spinner): >{len(end_msg) - 1}}', *self._get_color_args()), end='')
|
||||
print(colored(f"{next(spinner): >{len(end_msg) - 1}}", *self._get_color_args()), end="")
|
||||
else:
|
||||
print(colored(f'{next(spinner): >{len(end_msg)}}', *self._get_color_args()), end='')
|
||||
print(colored(f"{next(spinner): >{len(end_msg)}}", *self._get_color_args()), end="")
|
||||
time.sleep(0.1)
|
||||
back = ''
|
||||
back = ""
|
||||
for i in range(0, len(end_msg)):
|
||||
back += '\b'
|
||||
back += "\b"
|
||||
|
||||
print(back, end='')
|
||||
print(back, end="")
|
||||
sys.stdout.flush()
|
||||
|
||||
if not self._exit:
|
||||
print(colored(end_msg, *self._get_color_args()), end='')
|
||||
print(colored(end_msg, *self._get_color_args()), end="")
|
||||
|
||||
def stop_spinning(self):
|
||||
"""
|
||||
Stops the spinner
|
||||
:return:
|
||||
"""
|
||||
r"""Stops the spinner"""
|
||||
self._is_spinning = False
|
||||
time.sleep(0.1)
|
||||
|
||||
def exit(self):
|
||||
"""
|
||||
Stops the spinner
|
||||
:return:
|
||||
"""
|
||||
r"""Stops the spinner"""
|
||||
self._is_spinning = False
|
||||
self._exit = True
|
||||
time.sleep(0.1)
|
||||
3
src/cpl-core/cpl/core/database/__init__.py
Normal file
3
src/cpl-core/cpl/core/database/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .database_settings_name_enum import DatabaseSettingsNameEnum
|
||||
from .database_settings import DatabaseSettings
|
||||
from .table_abc import TableABC
|
||||
2
src/cpl-core/cpl/core/database/connection/__init__.py
Normal file
2
src/cpl-core/cpl/core/database/connection/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .database_connection import DatabaseConnection
|
||||
from .database_connection_abc import DatabaseConnectionABC
|
||||
@@ -0,0 +1,54 @@
|
||||
from typing import Optional
|
||||
|
||||
import mysql.connector as sql
|
||||
from mysql.connector.abstracts import MySQLConnectionAbstract
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
from cpl.core.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from cpl.core.utils.credential_manager import CredentialManager
|
||||
|
||||
|
||||
class DatabaseConnection(DatabaseConnectionABC):
|
||||
r"""Representation of the database connection"""
|
||||
|
||||
def __init__(self):
|
||||
DatabaseConnectionABC.__init__(self)
|
||||
|
||||
self._database: Optional[MySQLConnectionAbstract] = None
|
||||
self._cursor: Optional[MySQLCursorBuffered] = None
|
||||
|
||||
@property
|
||||
def server(self) -> MySQLConnectionAbstract:
|
||||
return self._database
|
||||
|
||||
@property
|
||||
def cursor(self) -> MySQLCursorBuffered:
|
||||
return self._cursor
|
||||
|
||||
def connect(self, settings: DatabaseSettings):
|
||||
connection = sql.connect(
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
user=settings.user,
|
||||
passwd=CredentialManager.decrypt(settings.password),
|
||||
charset=settings.charset,
|
||||
use_unicode=settings.use_unicode,
|
||||
buffered=settings.buffered,
|
||||
auth_plugin=settings.auth_plugin,
|
||||
ssl_disabled=settings.ssl_disabled,
|
||||
)
|
||||
connection.cursor().execute(f"CREATE DATABASE IF NOT EXISTS `{settings.database}`;")
|
||||
self._database = sql.connect(
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
user=settings.user,
|
||||
passwd=CredentialManager.decrypt(settings.password),
|
||||
db=settings.database,
|
||||
charset=settings.charset,
|
||||
use_unicode=settings.use_unicode,
|
||||
buffered=settings.buffered,
|
||||
auth_plugin=settings.auth_plugin,
|
||||
ssl_disabled=settings.ssl_disabled,
|
||||
)
|
||||
self._cursor = self._database.cursor()
|
||||
@@ -0,0 +1,32 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.abstracts import MySQLConnectionAbstract
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
class DatabaseConnectionABC(ABC):
|
||||
r"""ABC for the :class:`cpl.core.database.connection.database_connection.DatabaseConnection`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def server(self) -> MySQLConnectionAbstract:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def cursor(self) -> MySQLCursorBuffered:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def connect(self, database_settings: DatabaseSettings):
|
||||
r"""Connects to a database by connection string
|
||||
|
||||
Parameter:
|
||||
connection_string: :class:`str`
|
||||
Database connection string, see: https://docs.sqlalchemy.org/en/14/core/engines.html
|
||||
"""
|
||||
2
src/cpl-core/cpl/core/database/context/__init__.py
Normal file
2
src/cpl-core/cpl/core/database/context/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .database_context import DatabaseContext
|
||||
from .database_context_abc import DatabaseContextABC
|
||||
52
src/cpl-core/cpl/core/database/context/database_context.py
Normal file
52
src/cpl-core/cpl/core/database/context/database_context.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
from cpl.core.database.connection.database_connection import DatabaseConnection
|
||||
from cpl.core.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl.core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
class DatabaseContext(DatabaseContextABC):
|
||||
r"""Representation of the database context
|
||||
|
||||
Parameter:
|
||||
database_settings: :class:`cpl.core.database.database_settings.DatabaseSettings`
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
DatabaseContextABC.__init__(self)
|
||||
|
||||
self._db: DatabaseConnectionABC = DatabaseConnection()
|
||||
self._settings: Optional[DatabaseSettings] = None
|
||||
|
||||
@property
|
||||
def cursor(self) -> MySQLCursorBuffered:
|
||||
self._ping_and_reconnect()
|
||||
return self._db.cursor
|
||||
|
||||
def _ping_and_reconnect(self):
|
||||
try:
|
||||
self._db.server.ping(reconnect=True, attempts=3, delay=5)
|
||||
except Exception:
|
||||
# reconnect your cursor as you did in __init__ or wherever
|
||||
if self._settings is None:
|
||||
raise Exception("Call DatabaseContext.connect first")
|
||||
self.connect(self._settings)
|
||||
|
||||
def connect(self, database_settings: DatabaseSettings):
|
||||
if self._settings is None:
|
||||
self._settings = database_settings
|
||||
self._db.connect(database_settings)
|
||||
|
||||
self.save_changes()
|
||||
|
||||
def save_changes(self):
|
||||
self._ping_and_reconnect()
|
||||
self._db.server.commit()
|
||||
|
||||
def select(self, statement: str) -> list[tuple]:
|
||||
self._ping_and_reconnect()
|
||||
self._db.cursor.execute(statement)
|
||||
return self._db.cursor.fetchall()
|
||||
@@ -0,0 +1,40 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
class DatabaseContextABC(ABC):
|
||||
r"""ABC for the :class:`cpl.core.database.context.database_context.DatabaseContext`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def cursor(self) -> MySQLCursorBuffered:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def connect(self, database_settings: DatabaseSettings):
|
||||
r"""Connects to a database by connection settings
|
||||
|
||||
Parameter:
|
||||
database_settings :class:`cpl.core.database.database_settings.DatabaseSettings`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def save_changes(self):
|
||||
r"""Saves changes of the database"""
|
||||
|
||||
@abstractmethod
|
||||
def select(self, statement: str) -> list[tuple]:
|
||||
r"""Runs SQL Statements
|
||||
|
||||
Parameter:
|
||||
statement: :class:`str`
|
||||
|
||||
Returns:
|
||||
list: Fetched list of selected elements
|
||||
"""
|
||||
73
src/cpl-core/cpl/core/database/database_settings.py
Normal file
73
src/cpl-core/cpl/core/database/database_settings.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class DatabaseSettings(ConfigurationModelABC):
|
||||
r"""Represents settings for the database connection"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = None,
|
||||
port: int = 3306,
|
||||
user: str = None,
|
||||
password: str = None,
|
||||
database: str = None,
|
||||
charset: str = "utf8mb4",
|
||||
use_unicode: bool = False,
|
||||
buffered: bool = False,
|
||||
auth_plugin: str = "caching_sha2_password",
|
||||
ssl_disabled: bool = False,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._host: Optional[str] = host
|
||||
self._port: Optional[int] = port
|
||||
self._user: Optional[str] = user
|
||||
self._password: Optional[str] = password
|
||||
self._database: Optional[str] = database
|
||||
self._charset: Optional[str] = charset
|
||||
self._use_unicode: Optional[bool] = use_unicode
|
||||
self._buffered: Optional[bool] = buffered
|
||||
self._auth_plugin: Optional[str] = auth_plugin
|
||||
self._ssl_disabled: Optional[bool] = ssl_disabled
|
||||
|
||||
@property
|
||||
def host(self) -> Optional[str]:
|
||||
return self._host
|
||||
|
||||
@property
|
||||
def port(self) -> Optional[int]:
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def user(self) -> Optional[str]:
|
||||
return self._user
|
||||
|
||||
@property
|
||||
def password(self) -> Optional[str]:
|
||||
return self._password
|
||||
|
||||
@property
|
||||
def database(self) -> Optional[str]:
|
||||
return self._database
|
||||
|
||||
@property
|
||||
def charset(self) -> Optional[str]:
|
||||
return self._charset
|
||||
|
||||
@property
|
||||
def use_unicode(self) -> Optional[bool]:
|
||||
return self._use_unicode
|
||||
|
||||
@property
|
||||
def buffered(self) -> Optional[bool]:
|
||||
return self._buffered
|
||||
|
||||
@property
|
||||
def auth_plugin(self) -> Optional[str]:
|
||||
return self._auth_plugin
|
||||
|
||||
@property
|
||||
def ssl_disabled(self) -> Optional[bool]:
|
||||
return self._ssl_disabled
|
||||
@@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DatabaseSettingsNameEnum(Enum):
|
||||
host = "Host"
|
||||
port = "Port"
|
||||
user = "User"
|
||||
password = "Password"
|
||||
database = "Database"
|
||||
charset = "Charset"
|
||||
use_unicode = "UseUnicode"
|
||||
buffered = "Buffered"
|
||||
auth_plugin = "AuthPlugin"
|
||||
8
src/cpl-core/cpl/core/database/db_logger.py
Normal file
8
src/cpl-core/cpl/core/database/db_logger.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from cpl.core.log import Logger
|
||||
from cpl.core.typing import Source
|
||||
|
||||
|
||||
class DBLogger(Logger):
|
||||
|
||||
def __init__(self, source: Source):
|
||||
Logger.__init__(self, source, "db")
|
||||
37
src/cpl-core/cpl/core/database/table_abc.py
Normal file
37
src/cpl-core/cpl/core/database/table_abc.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class TableABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
self._created_at: Optional[datetime] = datetime.now().isoformat()
|
||||
self._modified_at: Optional[datetime] = datetime.now().isoformat()
|
||||
|
||||
@property
|
||||
def created_at(self) -> datetime:
|
||||
return self._created_at
|
||||
|
||||
@property
|
||||
def modified_at(self) -> datetime:
|
||||
return self._modified_at
|
||||
|
||||
@modified_at.setter
|
||||
def modified_at(self, value: datetime):
|
||||
self._modified_at = value
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def insert_string(self) -> str:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def udpate_string(self) -> str:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def delete_string(self) -> str:
|
||||
pass
|
||||
8
src/cpl-core/cpl/core/dependency_injection/__init__.py
Normal file
8
src/cpl-core/cpl/core/dependency_injection/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .scope import Scope
|
||||
from .scope_abc import ScopeABC
|
||||
from .service_collection import ServiceCollection
|
||||
from .service_collection_abc import ServiceCollectionABC
|
||||
from .service_descriptor import ServiceDescriptor
|
||||
from .service_lifetime_enum import ServiceLifetimeEnum
|
||||
from .service_provider import ServiceProvider
|
||||
from .service_provider_abc import ServiceProviderABC
|
||||
22
src/cpl-core/cpl/core/dependency_injection/scope.py
Normal file
22
src/cpl-core/cpl/core/dependency_injection/scope.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from cpl.core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class Scope(ScopeABC):
|
||||
def __init__(self, service_provider: ServiceProviderABC):
|
||||
self._service_provider = service_provider
|
||||
self._service_provider.set_scope(self)
|
||||
ScopeABC.__init__(self)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.dispose()
|
||||
|
||||
@property
|
||||
def service_provider(self) -> ServiceProviderABC:
|
||||
return self._service_provider
|
||||
|
||||
def dispose(self):
|
||||
self._service_provider = None
|
||||
21
src/cpl-core/cpl/core/dependency_injection/scope_abc.py
Normal file
21
src/cpl-core/cpl/core/dependency_injection/scope_abc.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ScopeABC(ABC):
|
||||
r"""ABC for the class :class:`cpl.core.dependency_injection.scope.Scope`"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def service_provider(self):
|
||||
r"""Returns to service provider of scope
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def dispose(self):
|
||||
r"""Sets service_provider to None"""
|
||||
18
src/cpl-core/cpl/core/dependency_injection/scope_builder.py
Normal file
18
src/cpl-core/cpl/core/dependency_injection/scope_builder.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from cpl.core.dependency_injection.scope import Scope
|
||||
from cpl.core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class ScopeBuilder:
|
||||
r"""Class to build :class:`cpl.core.dependency_injection.scope.Scope`"""
|
||||
|
||||
def __init__(self, service_provider: ServiceProviderABC) -> None:
|
||||
self._service_provider = service_provider
|
||||
|
||||
def build(self) -> ScopeABC:
|
||||
r"""Returns scope
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl.core.dependency_injection.scope.Scope`
|
||||
"""
|
||||
return Scope(self._service_provider)
|
||||
@@ -0,0 +1,79 @@
|
||||
from typing import Union, Type, Callable, Optional
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.core.dependency_injection.service_provider import ServiceProvider
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.log.logger import Logger
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
from cpl.core.typing import T, Service
|
||||
|
||||
|
||||
class ServiceCollection(ServiceCollectionABC):
|
||||
r"""Representation of the collection of services"""
|
||||
|
||||
def __init__(self, config: ConfigurationABC):
|
||||
ServiceCollectionABC.__init__(self)
|
||||
self._configuration: ConfigurationABC = config
|
||||
|
||||
self._database_context: Optional[DatabaseContextABC] = None
|
||||
self._service_descriptors: list[ServiceDescriptor] = []
|
||||
|
||||
def _add_descriptor(self, service: Union[type, object], lifetime: ServiceLifetimeEnum, base_type: Callable = None):
|
||||
found = False
|
||||
for descriptor in self._service_descriptors:
|
||||
if isinstance(service, descriptor.service_type):
|
||||
found = True
|
||||
|
||||
if found:
|
||||
service_type = service
|
||||
if not isinstance(service, type):
|
||||
service_type = type(service)
|
||||
|
||||
raise Exception(f"Service of type {service_type} already exists")
|
||||
|
||||
self._service_descriptors.append(ServiceDescriptor(service, lifetime, base_type))
|
||||
|
||||
def _add_descriptor_by_lifetime(self, service_type: Type, lifetime: ServiceLifetimeEnum, service: Callable = None):
|
||||
if service is not None:
|
||||
self._add_descriptor(service, lifetime, service_type)
|
||||
else:
|
||||
self._add_descriptor(service_type, lifetime)
|
||||
|
||||
return self
|
||||
|
||||
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
|
||||
self.add_singleton(DatabaseContextABC, db_context_type)
|
||||
self._database_context = self.build_service_provider().get_service(DatabaseContextABC)
|
||||
self._database_context.connect(db_settings)
|
||||
|
||||
def add_logging(self):
|
||||
self.add_transient(LoggerABC, Logger)
|
||||
return self
|
||||
|
||||
def add_pipes(self):
|
||||
for pipe in PipeABC.__subclasses__():
|
||||
self.add_transient(PipeABC, pipe)
|
||||
return self
|
||||
|
||||
def add_singleton(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
|
||||
return self
|
||||
|
||||
def add_scoped(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service)
|
||||
return self
|
||||
|
||||
def add_transient(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
|
||||
return self
|
||||
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
sp = ServiceProvider(self._service_descriptors, self._configuration, self._database_context)
|
||||
ServiceProviderABC.set_global_provider(sp)
|
||||
return sp
|
||||
@@ -0,0 +1,90 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Type
|
||||
|
||||
from cpl.core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.core.database.database_settings import DatabaseSettings
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.typing import T, Source
|
||||
|
||||
|
||||
class ServiceCollectionABC(ABC):
|
||||
r"""ABC for the class :class:`cpl.core.dependency_injection.service_collection.ServiceCollection`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
|
||||
r"""Adds database context
|
||||
|
||||
Parameter:
|
||||
db_context: Type[:class:`cpl.core.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database context
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_logging(self):
|
||||
r"""Adds the CPL internal logger"""
|
||||
|
||||
@abstractmethod
|
||||
def add_pipes(self):
|
||||
r"""Adds the CPL internal pipes as transient"""
|
||||
|
||||
def add_translation(self):
|
||||
r"""Adds the CPL translation"""
|
||||
raise NotImplementedError("You should install and use the cpl-translation package")
|
||||
|
||||
def add_mail(self):
|
||||
r"""Adds the CPL mail"""
|
||||
raise NotImplementedError("You should install and use the cpl-mail package")
|
||||
|
||||
@abstractmethod
|
||||
def add_transient(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
r"""Adds a service with transient lifetime
|
||||
|
||||
Parameter:
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_scoped(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
r"""Adds a service with scoped lifetime
|
||||
|
||||
Parameter:
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_singleton(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
r"""Adds a service with singleton lifetime
|
||||
|
||||
Parameter:
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
r"""Creates instance of the service provider
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
"""
|
||||
@@ -0,0 +1,46 @@
|
||||
from typing import Union, Optional
|
||||
|
||||
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
|
||||
|
||||
class ServiceDescriptor:
|
||||
r"""Descriptor of a service
|
||||
|
||||
Parameter:
|
||||
implementation: Union[:class:`type`, Optional[:class:`object`]]
|
||||
Object or type of service
|
||||
lifetime: :class:`cpl.core.dependency_injection.service_lifetime_enum.ServiceLifetimeEnum`
|
||||
Lifetime of the service
|
||||
"""
|
||||
|
||||
def __init__(self, implementation: Union[type, Optional[object]], lifetime: ServiceLifetimeEnum, base_type=None):
|
||||
self._service_type = implementation
|
||||
self._implementation = implementation
|
||||
self._lifetime = lifetime
|
||||
|
||||
if not isinstance(implementation, type):
|
||||
self._service_type = type(implementation)
|
||||
else:
|
||||
self._implementation = None
|
||||
|
||||
self._base_type = base_type if base_type is not None else self._service_type
|
||||
|
||||
@property
|
||||
def service_type(self) -> type:
|
||||
return self._service_type
|
||||
|
||||
@property
|
||||
def base_type(self) -> type:
|
||||
return self._base_type
|
||||
|
||||
@property
|
||||
def implementation(self) -> Union[type, Optional[object]]:
|
||||
return self._implementation
|
||||
|
||||
@implementation.setter
|
||||
def implementation(self, implementation: Union[type, Optional[object]]):
|
||||
self._implementation = implementation
|
||||
|
||||
@property
|
||||
def lifetime(self) -> ServiceLifetimeEnum:
|
||||
return self._lifetime
|
||||
@@ -0,0 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ServiceLifetimeEnum(Enum):
|
||||
singleton = 0
|
||||
scoped = 1
|
||||
transient = 2
|
||||
175
src/cpl-core/cpl/core/dependency_injection/service_provider.py
Normal file
175
src/cpl-core/cpl/core/dependency_injection/service_provider.py
Normal file
@@ -0,0 +1,175 @@
|
||||
import copy
|
||||
import typing
|
||||
from inspect import signature, Parameter, Signature
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl.core.dependency_injection.scope_builder import ScopeBuilder
|
||||
from cpl.core.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.core.typing import T, R, Source
|
||||
|
||||
|
||||
class ServiceProvider(ServiceProviderABC):
|
||||
r"""Provider for the services
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_descriptors: list[:class:`cpl.core.dependency_injection.service_descriptor.ServiceDescriptor`]
|
||||
Descriptor of the service
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
CPL Configuration
|
||||
db_context: Optional[:class:`cpl.core.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database representation
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
service_descriptors: list[ServiceDescriptor],
|
||||
config: ConfigurationABC,
|
||||
db_context: Optional[DatabaseContextABC],
|
||||
):
|
||||
ServiceProviderABC.__init__(self)
|
||||
|
||||
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
|
||||
self._configuration: ConfigurationABC = config
|
||||
self._database_context = db_context
|
||||
self._scope: Optional[ScopeABC] = None
|
||||
|
||||
def _find_service(self, service_type: type) -> Optional[ServiceDescriptor]:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == service_type or issubclass(descriptor.base_type, service_type):
|
||||
return descriptor
|
||||
|
||||
return None
|
||||
|
||||
def _get_service(self, parameter: Parameter, origin_service_type: type = None) -> Optional[object]:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == parameter.annotation or issubclass(
|
||||
descriptor.service_type, parameter.annotation
|
||||
):
|
||||
if descriptor.implementation is not None:
|
||||
return descriptor.implementation
|
||||
|
||||
implementation = self._build_service(descriptor.service_type, origin_service_type=origin_service_type)
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptor.implementation = implementation
|
||||
|
||||
return implementation
|
||||
|
||||
# raise Exception(f'Service {parameter.annotation} not found')
|
||||
|
||||
def _get_services(self, t: type, *args, service_type: type = None, **kwargs) -> list[Optional[object]]:
|
||||
implementations = []
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == t or issubclass(descriptor.service_type, t):
|
||||
if descriptor.implementation is not None:
|
||||
implementations.append(descriptor.implementation)
|
||||
continue
|
||||
|
||||
implementation = self._build_service(descriptor.service_type, *args, service_type, **kwargs)
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptor.implementation = implementation
|
||||
|
||||
implementations.append(implementation)
|
||||
|
||||
return implementations
|
||||
|
||||
def _build_by_signature(self, sig: Signature, origin_service_type: type) -> list[R]:
|
||||
params = []
|
||||
for param in sig.parameters.items():
|
||||
parameter = param[1]
|
||||
if parameter.name != "self" and parameter.annotation != Parameter.empty:
|
||||
if typing.get_origin(parameter.annotation) == list:
|
||||
params.append(self._get_services(typing.get_args(parameter.annotation)[0], origin_service_type))
|
||||
|
||||
elif parameter.annotation == Source:
|
||||
params.append(origin_service_type.__name__)
|
||||
|
||||
elif issubclass(parameter.annotation, ServiceProviderABC):
|
||||
params.append(self)
|
||||
|
||||
elif issubclass(parameter.annotation, ApplicationEnvironmentABC):
|
||||
params.append(self._configuration.environment)
|
||||
|
||||
elif issubclass(parameter.annotation, DatabaseContextABC):
|
||||
params.append(self._database_context)
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationModelABC):
|
||||
params.append(self._configuration.get_configuration(parameter.annotation))
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationABC):
|
||||
params.append(self._configuration)
|
||||
|
||||
else:
|
||||
params.append(self._get_service(parameter, origin_service_type))
|
||||
|
||||
return params
|
||||
|
||||
def _build_service(self, service_type: type, *args, origin_service_type: type = None, **kwargs) -> object:
|
||||
if origin_service_type is None:
|
||||
origin_service_type = service_type
|
||||
|
||||
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
|
||||
if descriptor.implementation is not None:
|
||||
service_type = type(descriptor.implementation)
|
||||
else:
|
||||
service_type = descriptor.service_type
|
||||
|
||||
break
|
||||
|
||||
sig = signature(service_type.__init__)
|
||||
params = self._build_by_signature(sig, origin_service_type)
|
||||
|
||||
return service_type(*params, *args, **kwargs)
|
||||
|
||||
def set_scope(self, scope: ScopeABC):
|
||||
self._scope = scope
|
||||
|
||||
def create_scope(self) -> ScopeABC:
|
||||
descriptors = []
|
||||
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptors.append(descriptor)
|
||||
else:
|
||||
descriptors.append(copy.deepcopy(descriptor))
|
||||
|
||||
sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context))
|
||||
return sb.build()
|
||||
|
||||
def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]:
|
||||
result = self._find_service(service_type)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
if result.implementation is not None:
|
||||
return result.implementation
|
||||
|
||||
implementation = self._build_service(service_type, *args, **kwargs)
|
||||
if (
|
||||
result.lifetime == ServiceLifetimeEnum.singleton
|
||||
or result.lifetime == ServiceLifetimeEnum.scoped
|
||||
and self._scope is not None
|
||||
):
|
||||
result.implementation = implementation
|
||||
|
||||
return implementation
|
||||
|
||||
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
|
||||
implementations = []
|
||||
|
||||
if typing.get_origin(service_type) == list:
|
||||
raise Exception(f"Invalid type {service_type}! Expected single type not list of type")
|
||||
|
||||
implementations.extend(self._get_services(service_type))
|
||||
|
||||
return implementations
|
||||
@@ -0,0 +1,111 @@
|
||||
import functools
|
||||
from abc import abstractmethod, ABC
|
||||
from inspect import Signature, signature
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl.core.typing import T, R
|
||||
|
||||
|
||||
class ServiceProviderABC(ABC):
|
||||
r"""ABC for the class :class:`cpl.core.dependency_injection.service_provider.ServiceProvider`"""
|
||||
|
||||
_provider: Optional["ServiceProviderABC"] = None
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def set_global_provider(cls, provider: "ServiceProviderABC"):
|
||||
cls._provider = provider
|
||||
|
||||
@abstractmethod
|
||||
def _build_by_signature(self, sig: Signature, origin_service_type: type) -> list[R]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _build_service(self, service_type: type, *args, **kwargs) -> object:
|
||||
r"""Creates instance of given type
|
||||
|
||||
Parameter
|
||||
---------
|
||||
instance_type: :class:`type`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of the given type
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set_scope(self, scope: ScopeABC):
|
||||
r"""Sets the scope of service provider
|
||||
|
||||
Parameter
|
||||
---------
|
||||
Object of type :class:`cpl.core.dependency_injection.scope_abc.ScopeABC`
|
||||
Service scope
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_scope(self) -> ScopeABC:
|
||||
r"""Creates a service scope
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type :class:`cpl.core.dependency_injection.scope_abc.ScopeABC`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_service(self, instance_type: T, *args, **kwargs) -> Optional[R]:
|
||||
r"""Returns instance of given type
|
||||
|
||||
Parameter
|
||||
---------
|
||||
instance_type: :class:`cpl.core.type.T`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type Optional[:class:`cpl.core.type.T`]
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
|
||||
r"""Returns instance of given type
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_type: :class:`cpl.core.type.T`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type list[Optional[:class:`cpl.core.type.T`]
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def inject(cls, f=None):
|
||||
r"""Decorator to allow injection into static and class methods
|
||||
|
||||
Parameter
|
||||
---------
|
||||
f: Callable
|
||||
|
||||
Returns
|
||||
-------
|
||||
function
|
||||
"""
|
||||
if f is None:
|
||||
return functools.partial(cls.inject)
|
||||
|
||||
@functools.wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
if cls._provider is None:
|
||||
raise Exception(f"{cls.__name__} not build!")
|
||||
|
||||
injection = [x for x in cls._provider._build_by_signature(signature(f)) if x is not None]
|
||||
return f(*args, *injection, **kwargs)
|
||||
|
||||
return inner
|
||||
3
src/cpl-core/cpl/core/environment/__init__.py
Normal file
3
src/cpl-core/cpl/core/environment/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .application_environment_abc import ApplicationEnvironmentABC
|
||||
from .environment_name_enum import EnvironmentNameEnum
|
||||
from .application_environment import ApplicationEnvironment
|
||||
95
src/cpl-core/cpl/core/environment/application_environment.py
Normal file
95
src/cpl-core/cpl/core/environment/application_environment.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.core.environment.environment_name_enum import EnvironmentNameEnum
|
||||
|
||||
|
||||
class ApplicationEnvironment(ApplicationEnvironmentABC):
|
||||
r"""Represents environment of the application
|
||||
|
||||
Parameter:
|
||||
name: :class:`cpl.core.environment.environment_name_enum.EnvironmentNameEnum`
|
||||
"""
|
||||
|
||||
def __init__(self, name: EnvironmentNameEnum = EnvironmentNameEnum.production):
|
||||
ApplicationEnvironmentABC.__init__(self)
|
||||
|
||||
self._environment_name: Optional[EnvironmentNameEnum] = name
|
||||
self._app_name: Optional[str] = None
|
||||
self._customer: Optional[str] = None
|
||||
|
||||
self._start_time: datetime = datetime.now()
|
||||
self._end_time: datetime = datetime.now()
|
||||
self._runtime_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
self._working_directory = os.getcwd()
|
||||
|
||||
@property
|
||||
def environment_name(self) -> str:
|
||||
return str(self._environment_name.value)
|
||||
|
||||
@environment_name.setter
|
||||
def environment_name(self, environment_name: str):
|
||||
self._environment_name = EnvironmentNameEnum(environment_name)
|
||||
|
||||
@property
|
||||
def application_name(self) -> str:
|
||||
return self._app_name if self._app_name is not None else ""
|
||||
|
||||
@application_name.setter
|
||||
def application_name(self, application_name: str):
|
||||
self._app_name = application_name
|
||||
|
||||
@property
|
||||
def customer(self) -> str:
|
||||
return self._customer if self._customer is not None else ""
|
||||
|
||||
@customer.setter
|
||||
def customer(self, customer: str):
|
||||
self._customer = customer
|
||||
|
||||
@property
|
||||
def host_name(self):
|
||||
return gethostname()
|
||||
|
||||
@property
|
||||
def start_time(self) -> datetime:
|
||||
return self._start_time
|
||||
|
||||
@property
|
||||
def end_time(self) -> datetime:
|
||||
return self._end_time
|
||||
|
||||
@end_time.setter
|
||||
def end_time(self, end_time: datetime):
|
||||
self._end_time = end_time
|
||||
|
||||
@property
|
||||
def date_time_now(self) -> datetime:
|
||||
return datetime.now()
|
||||
|
||||
@property
|
||||
def working_directory(self) -> str:
|
||||
return str(self._working_directory)
|
||||
|
||||
@property
|
||||
def runtime_directory(self) -> str:
|
||||
return str(self._runtime_directory)
|
||||
|
||||
def set_runtime_directory(self, runtime_directory: str):
|
||||
if runtime_directory != "":
|
||||
self._runtime_directory = runtime_directory
|
||||
return
|
||||
|
||||
self._runtime_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def set_working_directory(self, working_directory: str):
|
||||
if working_directory != "":
|
||||
self._working_directory = working_directory
|
||||
os.chdir(self._working_directory)
|
||||
return
|
||||
|
||||
self._working_directory = os.path.abspath("./")
|
||||
os.chdir(self._working_directory)
|
||||
@@ -0,0 +1,98 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ApplicationEnvironmentABC(ABC):
|
||||
r"""ABC of the class :class:`cpl.core.environment.application_environment.ApplicationEnvironment`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def environment_name(self) -> str:
|
||||
pass
|
||||
|
||||
@environment_name.setter
|
||||
@abstractmethod
|
||||
def environment_name(self, environment_name: str):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def application_name(self) -> str:
|
||||
pass
|
||||
|
||||
@application_name.setter
|
||||
@abstractmethod
|
||||
def application_name(self, application_name: str):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def customer(self) -> str:
|
||||
pass
|
||||
|
||||
@customer.setter
|
||||
@abstractmethod
|
||||
def customer(self, customer: str):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def host_name(self) -> str:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def start_time(self) -> datetime:
|
||||
pass
|
||||
|
||||
@start_time.setter
|
||||
@abstractmethod
|
||||
def start_time(self, start_time: datetime):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def end_time(self):
|
||||
pass
|
||||
|
||||
@end_time.setter
|
||||
@abstractmethod
|
||||
def end_time(self, end_time: datetime):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def date_time_now(self) -> datetime:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def working_directory(self) -> str:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def runtime_directory(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_runtime_directory(self, runtime_directory: str):
|
||||
r"""Sets the current runtime directory
|
||||
|
||||
Parameter:
|
||||
runtime_directory: :class:`str`
|
||||
Path of the runtime directory
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set_working_directory(self, working_directory: str):
|
||||
r"""Sets the current working directory
|
||||
|
||||
Parameter:
|
||||
working_directory: :class:`str`
|
||||
Path of the current working directory
|
||||
"""
|
||||
32
src/cpl-core/cpl/core/environment/environment.py
Normal file
32
src/cpl-core/cpl/core/environment/environment.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
from typing import Optional, Type
|
||||
|
||||
from cpl.core.typing import T
|
||||
from cpl.core.utils.get_value import get_value
|
||||
|
||||
|
||||
class Environment:
|
||||
_environment = "production"
|
||||
|
||||
@classmethod
|
||||
def get_environment(cls):
|
||||
return cls._environment
|
||||
|
||||
@classmethod
|
||||
def set_environment(cls, environment: str):
|
||||
if environment not in ["development", "staging", "production"]:
|
||||
raise ValueError("Invalid environment")
|
||||
Environment._environment = environment
|
||||
|
||||
@staticmethod
|
||||
def get(key: str, cast_type: Type[T], default: Optional[T] = None) -> Optional[T]:
|
||||
"""
|
||||
Get an environment variable and cast it to a specified type.
|
||||
:param str key: The name of the environment variable.
|
||||
:param Type[T] cast_type: A callable to cast the variable's value.
|
||||
:param Optional[T] default: The default value to return if the variable is not found. Defaults to None.The default value to return if the variable is not found. Defaults to None.
|
||||
:return: The casted value, or None if the variable is not found.
|
||||
:rtype: Optional[T]
|
||||
"""
|
||||
|
||||
return get_value(dict(os.environ), key, cast_type, default)
|
||||
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EnvironmentNameEnum(Enum):
|
||||
production = "production"
|
||||
staging = "staging"
|
||||
testing = "testing"
|
||||
development = "development"
|
||||
4
src/cpl-core/cpl/core/log/__init__.py
Normal file
4
src/cpl-core/cpl/core/log/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .logger import Logger
|
||||
from .logger_abc import LoggerABC
|
||||
from .log_level_enum import LogLevelEnum
|
||||
from .logging_settings import LogSettings
|
||||
94
src/cpl-core/cpl/core/log/_log_writer.py
Normal file
94
src/cpl-core/cpl/core/log/_log_writer.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Self
|
||||
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
|
||||
|
||||
class LogWriter:
|
||||
_instance = None
|
||||
|
||||
# ANSI color codes for different log levels
|
||||
_COLORS = {
|
||||
LogLevelEnum.trace: "\033[37m", # Light Gray
|
||||
LogLevelEnum.debug: "\033[94m", # Blue
|
||||
LogLevelEnum.info: "\033[92m", # Green
|
||||
LogLevelEnum.warning: "\033[93m", # Yellow
|
||||
LogLevelEnum.error: "\033[91m", # Red
|
||||
LogLevelEnum.fatal: "\033[95m", # Magenta
|
||||
}
|
||||
|
||||
def __init__(self, file_prefix: str, level: LogLevelEnum = LogLevelEnum.info):
|
||||
self._file_prefix = file_prefix
|
||||
self._level = level
|
||||
|
||||
self._queue = multiprocessing.Queue()
|
||||
self._process = multiprocessing.Process(target=self._log_worker, daemon=True)
|
||||
|
||||
self._create_log_dir()
|
||||
self._process.start()
|
||||
|
||||
@property
|
||||
def level(self) -> LogLevelEnum:
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, value: LogLevelEnum):
|
||||
assert isinstance(value, LogLevelEnum), "Log level must be an instance of LogLevelEnum"
|
||||
self._level = value
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, file_prefix: str, level: LogLevelEnum = LogLevelEnum.info) -> Self:
|
||||
if cls._instance is None:
|
||||
cls._instance = LogWriter(file_prefix, level)
|
||||
return cls._instance
|
||||
|
||||
@staticmethod
|
||||
def _create_log_dir():
|
||||
if os.path.exists("logs"):
|
||||
return
|
||||
|
||||
os.makedirs("logs")
|
||||
|
||||
def _log_worker(self):
|
||||
"""Worker process that writes log messages from the queue to the file."""
|
||||
while True:
|
||||
content = self._queue.get()
|
||||
if content is None: # Shutdown signal
|
||||
break
|
||||
self._write_log_to_file(content)
|
||||
Console.write_line(
|
||||
f"{self._COLORS.get(self._level, '\033[0m')}{content}\033[0m"
|
||||
)
|
||||
|
||||
@property
|
||||
def log_file(self):
|
||||
return f"logs/{self._file_prefix}_{datetime.now().strftime('%Y-%m-%d')}.log"
|
||||
|
||||
def _ensure_file_size(self):
|
||||
log_file = self.log_file
|
||||
if not os.path.exists(log_file) or os.path.getsize(log_file) <= 0.5 * 1024 * 1024:
|
||||
return
|
||||
|
||||
# if exists and size is greater than 300MB, create a new file
|
||||
os.rename(
|
||||
log_file,
|
||||
f"{log_file.split('.log')[0]}_{datetime.now().strftime('%H-%M-%S')}.log",
|
||||
)
|
||||
|
||||
def _write_log_to_file(self, content: str):
|
||||
self._ensure_file_size()
|
||||
with open(self.log_file, "a") as log_file:
|
||||
log_file.write(content + "\n")
|
||||
log_file.close()
|
||||
|
||||
def log(self, content: str):
|
||||
"""Enqueue log message without blocking main app."""
|
||||
self._queue.put(content)
|
||||
|
||||
def close(self):
|
||||
"""Gracefully stop the logging process."""
|
||||
self._queue.put(None)
|
||||
self._process.join()
|
||||
11
src/cpl-core/cpl/core/log/log_level_enum.py
Normal file
11
src/cpl-core/cpl/core/log/log_level_enum.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class LogLevelEnum(Enum):
|
||||
off = "OFF" # Nothing
|
||||
trace = "TRC" # Detailed app information's
|
||||
debug = "DEB" # Detailed app state
|
||||
info = "INF" # Normal information's
|
||||
warning = "WAR" # Error that can later be fatal
|
||||
error = "ERR" # Non fatal error
|
||||
fatal = "FAT" # Error that cause exit
|
||||
78
src/cpl-core/cpl/core/log/logger.py
Normal file
78
src/cpl-core/cpl/core/log/logger.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log._log_writer import LogWriter
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.typing import Messages, Source
|
||||
|
||||
|
||||
class Logger(LoggerABC):
|
||||
_level = LogLevelEnum.info
|
||||
_levels = [x for x in LogLevelEnum]
|
||||
|
||||
def __init__(self, source: Source, file_prefix: str = None):
|
||||
LoggerABC.__init__(self)
|
||||
assert source is not None and source != "", "Source cannot be None or empty"
|
||||
self._source = source
|
||||
|
||||
if file_prefix is None:
|
||||
file_prefix = "app"
|
||||
|
||||
self._file_prefix = file_prefix
|
||||
self._writer = LogWriter.get_instance(self._file_prefix)
|
||||
|
||||
@classmethod
|
||||
def set_level(cls, level: LogLevelEnum):
|
||||
if level in cls._levels:
|
||||
cls._level = level
|
||||
else:
|
||||
raise ValueError(f"Invalid log level: {level}")
|
||||
|
||||
def _log(self, level: LogLevelEnum, *messages: Messages):
|
||||
try:
|
||||
if self._levels.index(level) < self._levels.index(self._level):
|
||||
return
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
formatted_message = self._format_message(level.value, timestamp, *messages)
|
||||
|
||||
self._writer.log(formatted_message)
|
||||
except Exception as e:
|
||||
print(f"Error while logging: {e} -> {traceback.format_exc()}")
|
||||
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
if isinstance(messages, tuple):
|
||||
messages = list(messages)
|
||||
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
messages = [str(message) for message in messages if message is not None]
|
||||
|
||||
return f"<{timestamp}> [{level.upper():^3}] [{self._file_prefix}] - [{self._source}]: {' '.join(messages)}"
|
||||
|
||||
def header(self, string: str):
|
||||
self._log(LogLevelEnum.info, string)
|
||||
|
||||
def trace(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.trace, *messages)
|
||||
|
||||
def debug(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.debug, *messages)
|
||||
|
||||
def info(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.info, *messages)
|
||||
|
||||
def warning(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.warning, *messages)
|
||||
|
||||
def error(self, message, e: Exception = None):
|
||||
self._log(LogLevelEnum.error, message, f"{e} -> {traceback.format_exc()}" if e else None)
|
||||
|
||||
def fatal(self, message, e: Exception = None, prevent_quit: bool = False):
|
||||
self._log(LogLevelEnum.fatal, message, f"{e} -> {traceback.format_exc()}" if e else None)
|
||||
if not prevent_quit:
|
||||
exit(-1)
|
||||
94
src/cpl-core/cpl/core/log/logger_abc.py
Normal file
94
src/cpl-core/cpl/core/log/logger_abc.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from cpl.core.typing import Messages
|
||||
|
||||
|
||||
class LoggerABC(ABC):
|
||||
r"""ABC for :class:`cpl.core.log.logger_service.Logger`"""
|
||||
|
||||
@abstractmethod
|
||||
def set_level(self, level: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def header(self, string: str):
|
||||
r"""Writes a header message
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
String to write as header
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def trace(self, *messages: Messages):
|
||||
r"""Writes a trace message
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Message name
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def debug(self, *messages: Messages):
|
||||
r"""Writes a debug message
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Message name
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def info(self, *messages: Messages):
|
||||
r"""Writes an information
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Message name
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def warning(self, *messages: Messages):
|
||||
r"""Writes an warning
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Message name
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def error(self, messages: str, e: Exception = None):
|
||||
r"""Writes an error
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Error name
|
||||
message: :class:`str`
|
||||
Error message
|
||||
ex: :class:`Exception`
|
||||
Thrown exception
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def fatal(self, messages: str, e: Exception = None):
|
||||
r"""Writes an error and ends the program
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Error name
|
||||
message: :class:`str`
|
||||
Error message
|
||||
ex: :class:`Exception`
|
||||
Thrown exception
|
||||
"""
|
||||
53
src/cpl-core/cpl/core/log/logging_settings.py
Normal file
53
src/cpl-core/cpl/core/log/logging_settings.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
|
||||
|
||||
class LogSettings(ConfigurationModelABC):
|
||||
r"""Representation of logging settings"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str = None,
|
||||
filename: str = None,
|
||||
console_log_level: LogLevelEnum = None,
|
||||
file_log_level: LogLevelEnum = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
self._path: Optional[str] = path
|
||||
self._filename: Optional[str] = filename
|
||||
self._console: Optional[LogLevelEnum] = console_log_level
|
||||
self._level: Optional[LogLevelEnum] = file_log_level
|
||||
|
||||
@property
|
||||
def path(self) -> str:
|
||||
return self._path
|
||||
|
||||
@path.setter
|
||||
def path(self, path: str) -> None:
|
||||
self._path = path
|
||||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
return self._filename
|
||||
|
||||
@filename.setter
|
||||
def filename(self, filename: str) -> None:
|
||||
self._filename = filename
|
||||
|
||||
@property
|
||||
def console(self) -> LogLevelEnum:
|
||||
return self._console
|
||||
|
||||
@console.setter
|
||||
def console(self, console: LogLevelEnum) -> None:
|
||||
self._console = console
|
||||
|
||||
@property
|
||||
def level(self) -> LogLevelEnum:
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, level: LogLevelEnum) -> None:
|
||||
self._level = level
|
||||
3
src/cpl-core/cpl/core/pipes/__init__.py
Normal file
3
src/cpl-core/cpl/core/pipes/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .bool_pipe import BoolPipe
|
||||
from .ip_address_pipe import IPAddressPipe
|
||||
from .pipe_abc import PipeABC
|
||||
9
src/cpl-core/cpl/core/pipes/bool_pipe.py
Normal file
9
src/cpl-core/cpl/core/pipes/bool_pipe.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
|
||||
|
||||
class BoolPipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: bool, *args):
|
||||
return "True" if value else "False"
|
||||
24
src/cpl-core/cpl/core/pipes/ip_address_pipe.py
Normal file
24
src/cpl-core/cpl/core/pipes/ip_address_pipe.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
|
||||
|
||||
class IPAddressPipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: list[int], *args):
|
||||
string = ""
|
||||
|
||||
if len(value) != 4:
|
||||
raise Exception("Invalid IP")
|
||||
|
||||
for i in range(0, len(value)):
|
||||
byte = value[i]
|
||||
if byte > 255 or byte < 0:
|
||||
raise Exception("Invalid IP")
|
||||
|
||||
if i == len(value) - 1:
|
||||
string += f"{byte}"
|
||||
else:
|
||||
string += f"{byte}."
|
||||
|
||||
return string
|
||||
11
src/cpl-core/cpl/core/pipes/pipe_abc.py
Normal file
11
src/cpl-core/cpl/core/pipes/pipe_abc.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class PipeABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform(self, value: any, *args):
|
||||
pass
|
||||
17
src/cpl-core/cpl/core/pipes/version_pipe.py
Normal file
17
src/cpl-core/cpl/core/pipes/version_pipe.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from cpl_cli.configuration import VersionSettingsNameEnum
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
|
||||
|
||||
class VersionPipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: dict, *args):
|
||||
for atr in VersionSettingsNameEnum:
|
||||
if atr.value not in value:
|
||||
raise KeyError(atr.value)
|
||||
|
||||
v_str = f"{value[VersionSettingsNameEnum.major.value]}.{value[VersionSettingsNameEnum.minor.value]}"
|
||||
if value[VersionSettingsNameEnum.micro.value] is not None:
|
||||
v_str += f".{value[VersionSettingsNameEnum.micro.value]}"
|
||||
return v_str
|
||||
2
src/cpl-core/cpl/core/time/__init__.py
Normal file
2
src/cpl-core/cpl/core/time/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .time_format_settings import TimeFormatSettings
|
||||
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
|
||||
52
src/cpl-core/cpl/core/time/time_format_settings.py
Normal file
52
src/cpl-core/cpl/core/time/time_format_settings.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class TimeFormatSettings(ConfigurationModelABC):
|
||||
r"""Representation of time format settings"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
date_format: str = None,
|
||||
time_format: str = None,
|
||||
date_time_format: str = None,
|
||||
date_time_log_format: str = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
self._date_format: Optional[str] = date_format
|
||||
self._time_format: Optional[str] = time_format
|
||||
self._date_time_format: Optional[str] = date_time_format
|
||||
self._date_time_log_format: Optional[str] = date_time_log_format
|
||||
|
||||
@property
|
||||
def date_format(self) -> str:
|
||||
return self._date_format
|
||||
|
||||
@date_format.setter
|
||||
def date_format(self, date_format: str) -> None:
|
||||
self._date_format = date_format
|
||||
|
||||
@property
|
||||
def time_format(self) -> str:
|
||||
return self._time_format
|
||||
|
||||
@time_format.setter
|
||||
def time_format(self, time_format: str):
|
||||
self._time_format = time_format
|
||||
|
||||
@property
|
||||
def date_time_format(self) -> str:
|
||||
return self._date_time_format
|
||||
|
||||
@date_time_format.setter
|
||||
def date_time_format(self, date_time_format: str) -> None:
|
||||
self._date_time_format = date_time_format
|
||||
|
||||
@property
|
||||
def date_time_log_format(self):
|
||||
return self._date_time_log_format
|
||||
|
||||
@date_time_log_format.setter
|
||||
def date_time_log_format(self, date_time_now_format: str) -> None:
|
||||
self._date_time_log_format = date_time_now_format
|
||||
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TimeFormatSettingsNamesEnum(Enum):
|
||||
date_format = "DateFormat"
|
||||
time_format = "TimeFormat"
|
||||
date_time_format = "DateTimeFormat"
|
||||
date_time_log_format = "DateTimeLogFormat"
|
||||
9
src/cpl-core/cpl/core/typing.py
Normal file
9
src/cpl-core/cpl/core/typing.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from typing import TypeVar, Any
|
||||
|
||||
T = TypeVar("T")
|
||||
R = TypeVar("R")
|
||||
|
||||
Service = TypeVar("Service")
|
||||
Source = TypeVar("Source")
|
||||
|
||||
Messages = list[Any] | Any
|
||||
3
src/cpl-core/cpl/core/utils/__init__.py
Normal file
3
src/cpl-core/cpl/core/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .credential_manager import CredentialManager
|
||||
from .string import String
|
||||
from .pip import Pip
|
||||
46
src/cpl-core/cpl/core/utils/credential_manager.py
Normal file
46
src/cpl-core/cpl/core/utils/credential_manager.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import base64
|
||||
|
||||
|
||||
class CredentialManager:
|
||||
r"""Handles credential encryption and decryption"""
|
||||
|
||||
@staticmethod
|
||||
def encrypt(string: str) -> str:
|
||||
r"""Encode with base64
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
String to encode
|
||||
|
||||
Returns:
|
||||
Encoded string
|
||||
"""
|
||||
return base64.b64encode(string.encode("utf-8")).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def decrypt(string: str) -> str:
|
||||
r"""Decode with base64
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
String to decode
|
||||
|
||||
Returns:
|
||||
Decoded string
|
||||
"""
|
||||
return base64.b64decode(string).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def build_string(string: str, credentials: str):
|
||||
r"""Builds string with credentials in it
|
||||
|
||||
Parameter:
|
||||
string: :class:`str`
|
||||
String in which the variable is replaced by credentials
|
||||
credentials: :class:`str`
|
||||
String to encode
|
||||
|
||||
Returns:
|
||||
Decoded string
|
||||
"""
|
||||
return string.replace("$credentials", CredentialManager.decrypt(credentials))
|
||||
63
src/cpl-core/cpl/core/utils/get_value.py
Normal file
63
src/cpl-core/cpl/core/utils/get_value.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from typing import Type, Optional
|
||||
|
||||
from cpl.core.typing import T
|
||||
|
||||
|
||||
def get_value(
|
||||
source: dict,
|
||||
key: str,
|
||||
cast_type: Type[T],
|
||||
default: Optional[T] = None,
|
||||
list_delimiter: str = ",",
|
||||
) -> Optional[T]:
|
||||
"""
|
||||
Get value from source dictionary and cast it to a specified type.
|
||||
:param dict source: The source dictionary.
|
||||
:param str key: The name of the environment variable.
|
||||
:param Type[T] cast_type: A callable to cast the variable's value.
|
||||
:param Optional[T] default: The default value to return if the variable is not found. Defaults to None.
|
||||
:param str list_delimiter: The delimiter to split the value into a list. Defaults to ",".
|
||||
:return: The casted value, or None if the key is not found.
|
||||
:rtype: Optional[T]
|
||||
"""
|
||||
|
||||
if key not in source:
|
||||
return default
|
||||
|
||||
value = source[key]
|
||||
if isinstance(
|
||||
value,
|
||||
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__,
|
||||
):
|
||||
# Handle list[int] case explicitly
|
||||
if hasattr(cast_type, "__origin__") and cast_type.__origin__ == list:
|
||||
subtype = cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
||||
if subtype is not None:
|
||||
return [subtype(item) for item in value]
|
||||
return value
|
||||
|
||||
try:
|
||||
if cast_type == bool:
|
||||
return value.lower() in ["true", "1"]
|
||||
|
||||
if (
|
||||
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__
|
||||
) == list:
|
||||
if (
|
||||
not (value.startswith("[") and value.endswith("]"))
|
||||
and list_delimiter not in value
|
||||
):
|
||||
raise ValueError(
|
||||
"List values must be enclosed in square brackets or use a delimiter."
|
||||
)
|
||||
|
||||
if value.startswith("[") and value.endswith("]"):
|
||||
value = value[1:-1]
|
||||
|
||||
value = value.split(list_delimiter)
|
||||
subtype = cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
||||
return [subtype(item) if subtype is not None else item for item in value]
|
||||
|
||||
return cast_type(value)
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
53
src/cpl-core/cpl/core/utils/json_processor.py
Normal file
53
src/cpl-core/cpl/core/utils/json_processor.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import enum
|
||||
from inspect import signature, Parameter
|
||||
|
||||
from cpl.core.utils import String
|
||||
|
||||
|
||||
class JSONProcessor:
|
||||
@staticmethod
|
||||
def process(_t: type, values: dict) -> object:
|
||||
args = []
|
||||
kwargs = {}
|
||||
|
||||
sig = signature(_t.__init__)
|
||||
for param in sig.parameters.items():
|
||||
parameter = param[1]
|
||||
if parameter.name == "self" or parameter.annotation == Parameter.empty:
|
||||
continue
|
||||
|
||||
name = String.first_to_upper(String.convert_to_camel_case(parameter.name))
|
||||
name_first_lower = String.first_to_lower(name)
|
||||
if name in values or name_first_lower in values or name.upper() in values:
|
||||
value = ""
|
||||
if name in values:
|
||||
value = values[name]
|
||||
values.pop(name)
|
||||
elif name_first_lower in values:
|
||||
value = values[name_first_lower]
|
||||
values.pop(name_first_lower)
|
||||
else:
|
||||
value = values[name.upper()]
|
||||
values.pop(name.upper())
|
||||
|
||||
if isinstance(value, dict) and not issubclass(parameter.annotation, dict):
|
||||
value = JSONProcessor.process(parameter.annotation, value)
|
||||
|
||||
if issubclass(parameter.annotation, enum.Enum):
|
||||
value = parameter.annotation[value]
|
||||
|
||||
if type(value) != parameter.annotation:
|
||||
value = parameter.annotation(value)
|
||||
|
||||
args.append(value)
|
||||
|
||||
elif parameter.name == "kwargs" and parameter.annotation == dict:
|
||||
kwargs = values
|
||||
|
||||
elif parameter.default != Parameter.empty:
|
||||
args.append(parameter.default)
|
||||
|
||||
else:
|
||||
args.append(None)
|
||||
|
||||
return _t(*args, **kwargs)
|
||||
130
src/cpl-core/cpl/core/utils/pip.py
Normal file
130
src/cpl-core/cpl/core/utils/pip.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Pip:
|
||||
r"""Executes pip commands"""
|
||||
|
||||
_executable = sys.executable
|
||||
_env = os.environ
|
||||
|
||||
"""Getter"""
|
||||
|
||||
@classmethod
|
||||
def get_executable(cls) -> str:
|
||||
return cls._executable
|
||||
|
||||
"""Setter"""
|
||||
|
||||
@classmethod
|
||||
def set_executable(cls, executable: str):
|
||||
r"""Sets the executable
|
||||
|
||||
Parameter:
|
||||
executable: :class:`str`
|
||||
The python command
|
||||
"""
|
||||
if executable is None or executable == sys.executable:
|
||||
return
|
||||
|
||||
cls._executable = executable
|
||||
if not os.path.islink(cls._executable) or not os.path.isfile(executable):
|
||||
return
|
||||
|
||||
path = os.path.dirname(os.path.dirname(cls._executable))
|
||||
cls._env = os.environ
|
||||
if sys.platform == "win32":
|
||||
cls._env["PATH"] = f"{path}\\bin" + os.pathsep + os.environ.get("PATH", "")
|
||||
else:
|
||||
cls._env["PATH"] = f"{path}/bin" + os.pathsep + os.environ.get("PATH", "")
|
||||
cls._env["VIRTUAL_ENV"] = path
|
||||
|
||||
@classmethod
|
||||
def reset_executable(cls):
|
||||
r"""Resets the executable to system standard"""
|
||||
cls._executable = sys.executable
|
||||
|
||||
"""Public utils functions"""
|
||||
|
||||
@classmethod
|
||||
def get_package(cls, package: str) -> Optional[str]:
|
||||
r"""Gets given package py local pip list
|
||||
|
||||
Parameter:
|
||||
package: :class:`str`
|
||||
|
||||
Returns:
|
||||
The package name as string
|
||||
"""
|
||||
result = None
|
||||
with suppress(Exception):
|
||||
args = [cls._executable, "-m", "pip", "freeze", "--all"]
|
||||
|
||||
result = subprocess.check_output(args, stderr=subprocess.DEVNULL, env=cls._env)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
for p in str(result.decode()).split("\n"):
|
||||
if p.startswith(package):
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_outdated(cls) -> bytes:
|
||||
r"""Gets table of outdated packages
|
||||
|
||||
Returns:
|
||||
Bytes string of the command result
|
||||
"""
|
||||
args = [cls._executable, "-m", "pip", "list", "--outdated"]
|
||||
|
||||
return subprocess.check_output(args, env=cls._env)
|
||||
|
||||
@classmethod
|
||||
def install(cls, package: str, *args, source: str = None, stdout=None, stderr=None):
|
||||
r"""Installs given package
|
||||
|
||||
Parameter:
|
||||
package: :class:`str`
|
||||
The name of the package
|
||||
args: :class:`list`
|
||||
Arguments for the command
|
||||
source: :class:`str`
|
||||
Extra index URL
|
||||
stdout: :class:`str`
|
||||
Stdout of subprocess.run
|
||||
stderr: :class:`str`
|
||||
Stderr of subprocess.run
|
||||
"""
|
||||
pip_args = [cls._executable, "-m", "pip", "install"]
|
||||
|
||||
for arg in args:
|
||||
pip_args.append(arg)
|
||||
|
||||
pip_args.append(package)
|
||||
|
||||
if source is not None:
|
||||
pip_args.append(f"--extra-index-url")
|
||||
pip_args.append(source)
|
||||
|
||||
subprocess.run(pip_args, stdout=stdout, stderr=stderr, env=cls._env)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls, package: str, stdout=None, stderr=None):
|
||||
r"""Uninstalls given package
|
||||
|
||||
Parameter:
|
||||
package: :class:`str`
|
||||
The name of the package
|
||||
stdout: :class:`str`
|
||||
Stdout of subprocess.run
|
||||
stderr: :class:`str`
|
||||
Stderr of subprocess.run
|
||||
"""
|
||||
args = [cls._executable, "-m", "pip", "uninstall", "--yes", package]
|
||||
|
||||
subprocess.run(args, stdout=stdout, stderr=stderr, env=cls._env)
|
||||
92
src/cpl-core/cpl/core/utils/string.py
Normal file
92
src/cpl-core/cpl/core/utils/string.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import re
|
||||
import string
|
||||
import random
|
||||
|
||||
|
||||
class String:
|
||||
r"""Useful functions for strings"""
|
||||
|
||||
@staticmethod
|
||||
def convert_to_camel_case(chars: str) -> str:
|
||||
r"""Converts string to camel case
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String converted to CamelCase
|
||||
"""
|
||||
converted_name = chars
|
||||
char_set = string.punctuation + " "
|
||||
for char in char_set:
|
||||
if char in converted_name:
|
||||
converted_name = "".join(word.title() for word in converted_name.split(char))
|
||||
|
||||
return converted_name
|
||||
|
||||
@staticmethod
|
||||
def convert_to_snake_case(chars: str) -> str:
|
||||
r"""Converts string to snake case
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String converted to snake_case
|
||||
"""
|
||||
# convert to train-case to CamelCase
|
||||
if "_" in chars:
|
||||
chars = chars.replace("_", "-")
|
||||
|
||||
if "-" in chars:
|
||||
chars = "".join(word.title() for word in chars.split("-"))
|
||||
|
||||
if " " in chars:
|
||||
new_chars = ""
|
||||
for word in chars.split(" "):
|
||||
new_chars += String.first_to_upper(word)
|
||||
|
||||
chars = new_chars
|
||||
|
||||
pattern1 = re.compile(r"(.)([A-Z][a-z]+)")
|
||||
pattern2 = re.compile(r"([a-z0-9])([A-Z])")
|
||||
file_name = re.sub(pattern1, r"\1_\2", chars)
|
||||
return re.sub(pattern2, r"\1_\2", file_name).lower()
|
||||
|
||||
@staticmethod
|
||||
def first_to_upper(chars: str) -> str:
|
||||
r"""Converts first char to upper
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String with first char as upper
|
||||
"""
|
||||
return f"{chars[0].upper()}{chars[1:]}"
|
||||
|
||||
@staticmethod
|
||||
def first_to_lower(chars: str) -> str:
|
||||
r"""Converts first char to lower
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String with first char as lower
|
||||
"""
|
||||
return f"{chars[0].lower()}{chars[1:]}"
|
||||
|
||||
@staticmethod
|
||||
def random_string(chars: str, length: int) -> str:
|
||||
r"""Creates random string by given chars and length
|
||||
|
||||
Returns:
|
||||
String of random chars
|
||||
"""
|
||||
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
29
src/cpl-core/pyproject.toml
Normal file
29
src/cpl-core/pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-core"
|
||||
version = "2024.7.0"
|
||||
description = "CPL core"
|
||||
readme = "CPL core package"
|
||||
requires-python = ">=3.12"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "core", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
1
src/cpl-core/requirements.dev.txt
Normal file
1
src/cpl-core/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
5
src/cpl-core/requirements.txt
Normal file
5
src/cpl-core/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
art==6.5
|
||||
colorama==0.4.6
|
||||
tabulate==0.9.0
|
||||
termcolor==3.1.0
|
||||
mysql-connector-python==9.4.0
|
||||
26
src/cpl-mail/cpl/mail/__init__.py
Normal file
26
src/cpl-mail/cpl/mail/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from .abc.email_client_abc import EMailClientABC
|
||||
from .email_client import EMailClient
|
||||
from .email_client_settings import EMailClientSettings
|
||||
from .email_client_settings_name_enum import EMailClientSettingsNameEnum
|
||||
from .email_model import EMail
|
||||
from .mail_logger import MailLogger
|
||||
|
||||
|
||||
def add_mail(self):
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log import LoggerABC
|
||||
|
||||
try:
|
||||
self.add_singleton(EMailClientABC, EMailClient)
|
||||
self.add_transient(LoggerABC, MailLogger)
|
||||
except ImportError as e:
|
||||
Console.error("cpl-translation is not installed", str(e))
|
||||
|
||||
|
||||
def init():
|
||||
from cpl.core.dependency_injection import ServiceCollection
|
||||
|
||||
ServiceCollection.add_mail = add_mail
|
||||
|
||||
|
||||
init()
|
||||
24
src/cpl-mail/cpl/mail/abc/email_client_abc.py
Normal file
24
src/cpl-mail/cpl/mail/abc/email_client_abc.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from cpl.mail.email_model import EMail
|
||||
|
||||
|
||||
class EMailClientABC(ABC):
|
||||
"""ABC of :class:`cpl.mail.email_client_service.EMailClient`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
ABC.__init__(self)
|
||||
|
||||
@abstractmethod
|
||||
def connect(self):
|
||||
r"""Connects to server"""
|
||||
|
||||
@abstractmethod
|
||||
def send_mail(self, email: EMail):
|
||||
r"""Sends email
|
||||
|
||||
Parameter:
|
||||
email: :class:`cpl.mail.email.EMail`
|
||||
Object of the E-Mail to send
|
||||
"""
|
||||
88
src/cpl-mail/cpl/mail/email_client.py
Normal file
88
src/cpl-mail/cpl/mail/email_client.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import ssl
|
||||
from smtplib import SMTP
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.core.utils.credential_manager import CredentialManager
|
||||
from cpl.mail.abc.email_client_abc import EMailClientABC
|
||||
from cpl.mail.email_client_settings import EMailClientSettings
|
||||
from cpl.mail.email_model import EMail
|
||||
from cpl.mail.mail_logger import MailLogger
|
||||
|
||||
|
||||
class EMailClient(EMailClientABC):
|
||||
r"""Service to send emails
|
||||
|
||||
Parameter:
|
||||
environment: :class:`cpl.core.environment.application_environment_abc.ApplicationEnvironmentABC`
|
||||
Environment of the application
|
||||
logger: :class:`cpl.core.log.logger_abc.LoggerABC`
|
||||
The logger to use
|
||||
mail_settings: :class:`cpl.mail.email_client_settings.EMailClientSettings`
|
||||
Settings for mailing
|
||||
"""
|
||||
|
||||
def __init__(self, environment: ApplicationEnvironmentABC, logger: MailLogger, mail_settings: EMailClientSettings):
|
||||
EMailClientABC.__init__(self)
|
||||
|
||||
self._environment = environment
|
||||
self._mail_settings = mail_settings
|
||||
self._logger = logger
|
||||
|
||||
self._server: Optional[SMTP] = None
|
||||
|
||||
self.create()
|
||||
|
||||
def create(self):
|
||||
r"""Creates connection"""
|
||||
self._logger.trace(f"Started {__name__}.create")
|
||||
self.connect()
|
||||
self._logger.trace(f"Stopped {__name__}.create")
|
||||
|
||||
def connect(self):
|
||||
self._logger.trace(f"Started {__name__}.connect")
|
||||
try:
|
||||
self._logger.debug(f"Try to connect to {self._mail_settings.host}:{self._mail_settings.port}")
|
||||
self._server = SMTP(self._mail_settings.host, self._mail_settings.port)
|
||||
self._logger.info(f"Connected to {self._mail_settings.host}:{self._mail_settings.port}")
|
||||
|
||||
self._logger.debug("Try to start tls")
|
||||
self._server.starttls(context=ssl.create_default_context())
|
||||
self._logger.info("Started tls")
|
||||
except Exception as e:
|
||||
self._logger.error("Cannot connect to mail server", e)
|
||||
|
||||
self._logger.trace(f"Stopped {__name__}.connect")
|
||||
|
||||
def login(self):
|
||||
r"""Login to server"""
|
||||
self._logger.trace(f"Started {__name__}.login")
|
||||
try:
|
||||
self._logger.debug(
|
||||
__name__,
|
||||
f"Try to login {self._mail_settings.user_name}@{self._mail_settings.host}:{self._mail_settings.port}",
|
||||
)
|
||||
self._server.login(
|
||||
self._mail_settings.user_name, CredentialManager.decrypt(self._mail_settings.credentials)
|
||||
)
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Logged on as {self._mail_settings.user_name} to {self._mail_settings.host}:{self._mail_settings.port}",
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error("Cannot login to mail server", e)
|
||||
|
||||
self._logger.trace(f"Stopped {__name__}.login")
|
||||
|
||||
def send_mail(self, email: EMail):
|
||||
self._logger.trace(f"Started {__name__}.send_mail")
|
||||
try:
|
||||
self.login()
|
||||
self._logger.debug(f"Try to send email to {email.receiver_list}")
|
||||
self._server.sendmail(
|
||||
self._mail_settings.user_name, email.receiver_list, email.get_content(self._mail_settings.user_name)
|
||||
)
|
||||
self._logger.info(f"Sent email to {email.receiver_list}")
|
||||
except Exception as e:
|
||||
self._logger.error(f"Cannot send mail to {email.receiver_list}", e)
|
||||
self._logger.trace(f"Stopped {__name__}.send_mail")
|
||||
51
src/cpl-mail/cpl/mail/email_client_settings.py
Normal file
51
src/cpl-mail/cpl/mail/email_client_settings.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class EMailClientSettings(ConfigurationModelABC):
|
||||
r"""Representation of mailing settings"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = None,
|
||||
port: int = None,
|
||||
user_name: str = None,
|
||||
credentials: str = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._host: str = host
|
||||
self._port: int = port
|
||||
self._user_name: str = user_name
|
||||
self._credentials: str = credentials
|
||||
|
||||
@property
|
||||
def host(self) -> str:
|
||||
return self._host
|
||||
|
||||
@host.setter
|
||||
def host(self, host: str) -> None:
|
||||
self._host = host
|
||||
|
||||
@property
|
||||
def port(self) -> int:
|
||||
return self._port
|
||||
|
||||
@port.setter
|
||||
def port(self, port: int) -> None:
|
||||
self._port = port
|
||||
|
||||
@property
|
||||
def user_name(self) -> str:
|
||||
return self._user_name
|
||||
|
||||
@user_name.setter
|
||||
def user_name(self, user_name: str) -> None:
|
||||
self._user_name = user_name
|
||||
|
||||
@property
|
||||
def credentials(self) -> str:
|
||||
return self._credentials
|
||||
|
||||
@credentials.setter
|
||||
def credentials(self, credentials: str) -> None:
|
||||
self._credentials = credentials
|
||||
8
src/cpl-mail/cpl/mail/email_client_settings_name_enum.py
Normal file
8
src/cpl-mail/cpl/mail/email_client_settings_name_enum.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EMailClientSettingsNameEnum(Enum):
|
||||
host = "Host"
|
||||
port = "Port"
|
||||
user_name = "UserName"
|
||||
credentials = "Credentials"
|
||||
@@ -2,16 +2,29 @@ import re
|
||||
|
||||
|
||||
class EMail:
|
||||
r"""Represents an email
|
||||
|
||||
def __init__(self, header: list[str] = None, subject: str = None, body: str = None, transceiver: str = None, receiver: list[str] = None):
|
||||
"""
|
||||
Represents an email
|
||||
:param header:
|
||||
:param subject:
|
||||
:param body:
|
||||
:param transceiver:
|
||||
:param receiver:
|
||||
"""
|
||||
Parameter:
|
||||
header: list[:class:`str`]
|
||||
Header of the E-Mail
|
||||
subject: :class:`str`
|
||||
Subject of the E-Mail
|
||||
body: :class:`str`
|
||||
Body of the E-Mail
|
||||
transceiver: :class:`str`
|
||||
Transceiver of the E-Mail
|
||||
receiver: list[:class:`str`]
|
||||
Receiver of the E-Mail
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
header: list[str] = None,
|
||||
subject: str = None,
|
||||
body: str = None,
|
||||
transceiver: str = None,
|
||||
receiver: list[str] = None,
|
||||
):
|
||||
self._header: list[str] = header
|
||||
|
||||
self._subject: str = subject
|
||||
@@ -22,7 +35,7 @@ class EMail:
|
||||
|
||||
@property
|
||||
def header(self) -> str:
|
||||
return '\r\n'.join(self._header)
|
||||
return "\r\n".join(self._header)
|
||||
|
||||
@property
|
||||
def header_list(self) -> list[str]:
|
||||
@@ -57,11 +70,11 @@ class EMail:
|
||||
if self.check_mail(transceiver):
|
||||
self._transceiver = transceiver
|
||||
else:
|
||||
raise Exception(f'Invalid email: {transceiver}')
|
||||
raise Exception(f"Invalid email: {transceiver}")
|
||||
|
||||
@property
|
||||
def receiver(self) -> str:
|
||||
return ','.join(self._receiver)
|
||||
return ",".join(self._receiver)
|
||||
|
||||
@property
|
||||
def receiver_list(self) -> list[str]:
|
||||
@@ -73,18 +86,23 @@ class EMail:
|
||||
|
||||
@staticmethod
|
||||
def check_mail(address: str) -> bool:
|
||||
r"""Checks if an email is valid
|
||||
|
||||
Parameter:
|
||||
address: :class:`str`
|
||||
The address to check
|
||||
|
||||
Returns:
|
||||
Result if E-Mail is valid or not
|
||||
"""
|
||||
Checks if an email is valid
|
||||
:param address:
|
||||
:return:
|
||||
"""
|
||||
return bool(re.search('^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(.\\w{2,3})+$', address))
|
||||
return bool(re.search("^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(.\\w{2,3})+$", address))
|
||||
|
||||
def add_header(self, header: str):
|
||||
"""
|
||||
Adds header
|
||||
:param header:
|
||||
:return:
|
||||
r"""Adds header
|
||||
|
||||
Parameter:
|
||||
header: :class:`str`
|
||||
The header of the E-Mail
|
||||
"""
|
||||
if self._header is None:
|
||||
self._header = []
|
||||
@@ -92,10 +110,11 @@ class EMail:
|
||||
self._header.append(header)
|
||||
|
||||
def add_receiver(self, receiver: str):
|
||||
"""
|
||||
Adds receiver
|
||||
:param receiver:
|
||||
:return:
|
||||
r"""Adds receiver
|
||||
|
||||
Parameter:
|
||||
receiver: :class:`str`
|
||||
The receiver of the E-Mail
|
||||
"""
|
||||
if self._receiver is None:
|
||||
self._receiver = []
|
||||
@@ -103,12 +122,18 @@ class EMail:
|
||||
if self.check_mail(receiver):
|
||||
self._receiver.append(receiver)
|
||||
else:
|
||||
raise Exception(f'Invalid email: {receiver}')
|
||||
raise Exception(f"Invalid email: {receiver}")
|
||||
|
||||
def get_content(self, transceiver: str):
|
||||
r"""Returns the mail as string
|
||||
|
||||
Parameter:
|
||||
transceiver: :class:`str`
|
||||
The transceiver of the E-Mail
|
||||
|
||||
Returns:
|
||||
E-Mail as string
|
||||
"""
|
||||
Returns the mail as string
|
||||
:param transceiver:
|
||||
:return:
|
||||
"""
|
||||
return str(f'From: {transceiver}\r\nTo: {self.receiver}\r\n{self.header}\r\nSubject: {self.subject}\r\n{self.body}').encode('utf-8')
|
||||
return str(
|
||||
f"From: {transceiver}\r\nTo: {self.receiver}\r\n{self.header}\r\nSubject: {self.subject}\r\n{self.body}"
|
||||
).encode("utf-8")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user