238 Commits

Author SHA1 Message Date
caca34168e Merge pull request '2021.4' (#19) from 2021.4 into master
Reviewed-on: sh-edraft.de/sh_common_py_lib#19
2021-04-01 10:13:32 +02:00
18b8ac589d Publish 2021.4.0 2021-04-01 10:04:13 +02:00
d85e628784 Improved readme 2021-03-31 15:51:14 +02:00
1e150cf10c Improved readme 2021-03-31 15:49:52 +02:00
9b0f0b7559 Improved readme 2021-03-31 15:49:00 +02:00
cc118b5030 Merge pull request '2021.04.03 - Readme' (#18) from 2021.04.03 into 2021.4
Reviewed-on: sh-edraft.de/sh_common_py_lib#18
2021-03-31 15:42:40 +02:00
3e7ff78861 Merge branch '2021.4' into 2021.04.03 2021-03-31 15:40:18 +02:00
448d538d21 Updated reame 2021-03-31 15:38:26 +02:00
d368161eb3 Publish 2021.4.0rc7 2021-03-31 12:11:55 +02:00
eef8488ca2 Bugfixes for new lib 2021-03-31 12:11:19 +02:00
b22d04b145 Publish 2021.4.0rc6 2021-03-31 12:03:15 +02:00
6579e54c2b Improved windows support 2021-03-31 12:02:51 +02:00
88c0153ca2 Improved windows support for publisher 2021-03-31 11:55:46 +02:00
f28c6ab309 Removed prints 2021-03-31 11:51:20 +02:00
976041f812 Publish 2021.4.0rc5 2021-03-31 11:48:26 +02:00
7693dd462f Bugfixes for publisher windows support 2021-03-31 11:47:47 +02:00
df5e61cf98 Bugfixes for publisher windows support 2021-03-31 11:47:28 +02:00
df87d08be3 Bugfix for live server windows support 2021-03-31 11:41:16 +02:00
33cb35e26f Publish 2021.4.0rc4.post1 2021-03-31 11:25:07 +02:00
18b2cd8f2f Publish 2021.4.0rc4 2021-03-31 11:01:08 +02:00
7ab2a881e8 Bugfixes 2021-03-31 10:47:14 +02:00
960de880ee Bugfixes 2021-03-31 10:44:54 +02:00
c0d8904782 Bugfixes for the command new 2021-03-31 10:19:54 +02:00
b6d508526a Bugfixes for the command new 2021-03-31 09:46:41 +02:00
31307485f0 Bugfixes in new command template 2021-03-31 09:13:04 +02:00
612c3583f1 Removed ununsed files 2021-03-30 21:02:36 +02:00
6cdd3376f1 Publish 2021.4.0rc3 2021-03-30 21:01:10 +02:00
b947789779 Bugfixes 2021-03-30 20:59:09 +02:00
0da83935fc Bugfixes 2021-03-30 12:44:31 +02:00
3428c70b69 Bugfixes 2021-03-30 11:54:44 +02:00
3d511ff727 Bugfixes 2021-03-30 11:53:07 +02:00
cf2f293290 Publish 2021.4.0rc2 2021-03-30 11:03:16 +02:00
c39601a35d Improved publisher 2021-03-30 11:02:58 +02:00
5f36e0a43e Improved publisher 2021-03-30 10:43:39 +02:00
31fe1c5cd7 Improved service collection 2021-03-30 10:29:50 +02:00
0cdecd893a Improved database context 2021-03-30 10:06:07 +02:00
260fe06be8 Improved new command 2021-03-30 09:54:04 +02:00
8ddfd35a82 Improved generate command 2021-03-30 09:47:47 +02:00
099bae3d7c Removed usage of ServiceABC 2021-03-30 09:27:45 +02:00
9758e1fae2 Improved update command 2021-03-29 16:16:27 +02:00
211143d6ab Package update 2021-03-29 16:06:54 +02:00
70b49a098a Publish 2021.4.0rc1 2021-03-29 15:59:05 +02:00
e2544f90fe Merge pull request '2021.4.2' (#12) from 2021.4.2 into 2021.4
Reviewed-on: sh-edraft.de/sh_common_py_lib#12
2021-03-29 15:48:58 +02:00
c223524d1b Removed direct import 2021-03-29 15:47:41 +02:00
1789e2671a Publish 2021.4.2 2021-03-29 15:26:45 +02:00
d0812bcf5c Added database table test 2021-03-29 14:29:17 +02:00
4d5368e5fc Bugfixes & improved database context handling 2021-03-29 14:18:58 +02:00
f3be927440 Removed service abc 2021-03-29 13:29:26 +02:00
d0e3e1792f Added db context handling 2021-03-29 13:25:25 +02:00
31793fb290 Added db context handling 2021-03-29 13:25:15 +02:00
41ddc86961 Bugfixes 2021-03-29 12:16:12 +02:00
30ef74dd18 Added generated simple di test app 2021-03-29 11:48:25 +02:00
bc69476bb2 Improved new project templates & publish 2021.4.2.dev1 2021-03-29 11:47:49 +02:00
05b9fd801b Bugfixes 2021-03-29 09:21:58 +02:00
7af7f866c1 Removed application runtime | Code refactoring p.2 2021-03-29 08:56:18 +02:00
069e44bee6 Code refactoring p.1 2021-03-27 08:41:33 +01:00
3c0dd00a99 Bugfixes 2021-03-26 13:16:40 +01:00
afb91c8f1f Improved service provider 2021-03-23 21:42:08 +01:00
ca51f88d2b Improved service provider 2021-03-23 21:39:29 +01:00
a683b70c5f [WIP] Improved service collection 2021-03-23 20:23:30 +01:00
c650e87443 [WIP] Added first changes for the DI 2021-03-23 20:21:24 +01:00
36dfedf918 Bugfixes cpl cli output 2021-03-23 17:31:24 +01:00
252f2b55fe Bugfixes in templates 2021-03-23 17:29:13 +01:00
99091a2540 Publish for 2021.4.2 2021-03-21 20:22:04 +01:00
c3fdb7a049 Changed version 2021-03-21 20:05:51 +01:00
5ba02688a6 Merge pull request '2021.04.01' (#11) from 2021.04.01 into 2021.04
Reviewed-on: sh-edraft.de/sh_common_py_lib#11
2021-03-21 20:04:23 +01:00
d189d32d96 Bugfixes & publish 2021.4.1.post15 2021-03-21 20:01:50 +01:00
59e111e593 Bugfixes 2021-03-19 14:36:32 +01:00
bfa029f207 Bugfixes 2021-03-19 14:20:57 +01:00
e2e763a424 Removed application host 2021-03-19 14:15:53 +01:00
132d2805ee Removed unused imports 2021-03-17 11:00:45 +01:00
b800152268 Test package update 2021-03-17 09:13:59 +01:00
e49ec43fc4 Bugfixes & Publish 2021.4.1.post13 2021-03-17 09:12:10 +01:00
63599a9ade Publish 2021.4.1.post12 2021-03-17 09:06:07 +01:00
a2313ac952 Improved python executable handling 2021-03-17 08:55:23 +01:00
a1cd939aa1 Bugfixes in install command 2021-03-17 08:37:50 +01:00
f69500418e Bugfixes in install command 2021-03-17 08:12:56 +01:00
5669807628 Bugfixes in install command 2021-03-17 07:59:12 +01:00
59fc139cfc Bugfixes in install command 2021-03-16 23:05:28 +01:00
b48754e5db Bugfixes in install command 2021-03-16 23:01:57 +01:00
1a61f2cd7c Bugfixes in install command 2021-03-16 23:00:22 +01:00
02831c71c9 Bugfixes in install command 2021-03-16 22:58:00 +01:00
3764f2d5bd Bugfixes in install command 2021-03-16 22:55:00 +01:00
a12a54f671 Bugfixes in install command 2021-03-16 22:46:04 +01:00
f35f5167f0 Bugfixes in install command 2021-03-16 22:39:18 +01:00
dbb0a4a2df Bugfixes in live development server 2021-03-16 22:29:30 +01:00
bc99763329 Command handling config bugfix 2021-03-16 22:09:42 +01:00
ecd3e77a38 Command handling config bugfix 2021-03-16 22:06:37 +01:00
8ef1416b35 Bugfixes for windows support 2021-03-16 21:50:16 +01:00
Sven Heidemann
dffa1ac703 Bugfixes for windows support 2021-03-16 21:37:12 +01:00
d7becdd9bc Added fixed paths imports 2021-03-16 19:04:30 +01:00
23a6207f54 Spinner bugfix 2021-03-16 18:55:58 +01:00
2769afc1e4 Bugfixes for cli 2021-03-16 18:28:41 +01:00
08836c1045 Bugfixes for cli 2021-03-16 18:28:31 +01:00
121b9b84a0 Console spinner bugfixes for linux terminal 2021-03-15 20:33:20 +01:00
26ec121079 Console select menu bugfixes for linux terminal 2021-03-15 20:06:34 +01:00
fdb9769fd2 Bugfixes 2021-03-15 18:50:53 +01:00
6742023919 Bugfixes 2021-03-15 18:47:08 +01:00
d61da17ebd Publish 2021.4.1.post5 2021-03-15 18:37:21 +01:00
57bae9f291 Console bugfixes 2021-03-15 18:36:59 +01:00
6596364027 Publish 2021.4.1.post4 2021-03-15 18:29:04 +01:00
85cd9fbcbc Smaller improvements 2021-03-15 18:28:33 +01:00
3e59f01add Bugfixes with live development server 2021-03-15 18:25:53 +01:00
bdb0548bc8 Removed docs 2021-03-14 17:04:18 +01:00
c1fe8c611c Added comments to application 2021-03-14 16:57:20 +01:00
3f56247aa7 Added comments to configuration 2021-03-14 16:46:21 +01:00
84680c0c44 Added comments to console 2021-03-14 16:40:01 +01:00
094a3c2aa9 Improved comments of database 2021-03-14 16:28:23 +01:00
51cec3062d Added comments to database 2021-03-14 16:27:06 +01:00
268d56c348 Added comments to dependency_injection 2021-03-14 16:25:11 +01:00
d1f1627214 Improved comments of environment 2021-03-14 16:20:29 +01:00
a604fca5cc Improved comments of mailing 2021-03-14 16:18:11 +01:00
c3e9803cd6 Improved comments of logging 2021-03-14 16:18:04 +01:00
9e7c6a695f Improved comments of logging 2021-03-14 16:16:08 +01:00
f75699f4ed Added comments to logging 2021-03-14 16:13:07 +01:00
40fb4fb3f9 Added comments to mailing 2021-03-14 16:08:52 +01:00
ac1b4b1a2a Added comments 2021-03-14 16:06:23 +01:00
cbf333564c Added comments 2021-03-14 16:01:15 +01:00
d5d8c3f413 Improved help command 2021-03-14 15:26:08 +01:00
a9f3f75a79 Improved help command 2021-03-14 15:15:07 +01:00
b52930daa5 Bugfixes and publish 2021.4.1post1 2021-03-14 15:11:36 +01:00
4390fea084 Bugfixes 2021-03-14 14:59:17 +01:00
c41e3c0d78 Bugfixes 2021-03-14 14:47:53 +01:00
7ffd553709 Bugfixes 2021-03-14 13:22:10 +01:00
5c012ab4a0 Publish version 2021.4.1post1 2021-03-14 12:11:02 +01:00
340906c3f4 Bugfixes 2021-03-14 12:07:40 +01:00
47256ab00d Bugfixes 2021-03-14 12:07:24 +01:00
072f58a2e9 Bugfixes 2021-03-14 12:05:57 +01:00
02b4f78baf Bugfixes 2021-03-14 11:54:03 +01:00
49f85acb6c Bugfixes 2021-03-14 11:33:29 +01:00
5008594c27 Bugfixes 2021-03-14 11:27:01 +01:00
d0af34ea25 Bugfixes 2021-03-14 11:15:43 +01:00
da582a8d88 Added uninstall command 2021-03-14 10:58:59 +01:00
fc12d1803d Improved update command 2021-03-14 10:33:57 +01:00
e7796e0371 Improved install command 2021-03-14 10:31:45 +01:00
6094b11068 Added install package command 2021-03-13 22:53:28 +01:00
a169c31ed5 Bugfixes 2021-03-13 22:32:11 +01:00
0522467b76 Bugfixes 2021-03-13 22:03:24 +01:00
534982b18d Added update command 2021-03-13 21:39:59 +01:00
2778ac0b4d Bugfixes 2021-03-13 21:36:04 +01:00
1ba698d12a Bugfixes 2021-03-13 15:59:08 +01:00
8d49d6491c Bugfixes 2021-03-13 14:25:08 +01:00
787d285329 Improved live dev server 2021-03-13 11:15:15 +01:00
dcbd13de1e Added live development server 2021-03-13 11:05:17 +01:00
4af18b6c70 Implemented live development server 2021-03-12 22:53:02 +01:00
86089a037c Improved console spinner 2021-03-12 20:43:08 +01:00
3d001ade0a Bugfixes 2021-03-12 20:37:31 +01:00
6a1e7d4063 Formatting 2021-03-12 18:07:10 +01:00
15b1a82cce Added select menu to console 2021-03-12 18:06:05 +01:00
9663e542f1 Improved spinner 2021-03-12 16:16:31 +01:00
f36d8c3e9f Improved test structure 2021-03-12 16:13:17 +01:00
db21d88721 Packaging 2021-03-12 16:10:43 +01:00
2580d4b6cf Renamed by internal conventions 2021-03-12 16:06:30 +01:00
b3b65f3dd2 Added thread schematic 2021-03-12 16:01:44 +01:00
ac63751583 Renamed by internal convention 2021-03-12 15:58:30 +01:00
8a95c03038 Bugfixes 2021-03-12 15:44:55 +01:00
650f632c17 Bugfixes 2021-03-12 15:35:54 +01:00
570e9b0215 Changed naming convention name.schematic.py to name_schematic.py 2021-03-12 15:35:34 +01:00
c9b612d552 Added all schematics to command generate 2021-03-12 15:32:37 +01:00
2ad97d5021 Bugfixes 2021-03-11 09:43:45 +01:00
82ffdfe278 Added generate abc command 2021-03-10 22:29:42 +01:00
0ff9920e9e Added string class to utils 2021-03-10 22:11:05 +01:00
0b6b205819 Bugfixes 2021-03-10 21:42:59 +01:00
db34f62fde Bugfixes 2021-03-10 21:41:40 +01:00
973fd39f44 Improved colors 2021-03-10 20:55:33 +01:00
d6278a3f4e Improved colors 2021-03-10 20:53:08 +01:00
ae4709b0d4 Improved spinner 2021-03-10 20:34:00 +01:00
b377710931 Improved new console command 2021-03-10 17:04:53 +01:00
d9d2003087 Improved console 2021-03-10 17:04:28 +01:00
9a713129dc Improved console argument handling 2021-03-10 16:22:38 +01:00
c672f53d3f Improved console argument handling 2021-03-10 15:00:50 +01:00
0e6b0a657b Improved commands 2021-03-10 14:29:35 +01:00
4e24662d90 Bugfixes 2021-03-10 14:16:03 +01:00
1bff2ea168 Refactoring 2021-03-10 14:07:52 +01:00
6e33f0bcae Improved application 2021-03-10 11:30:25 +01:00
9a149ec341 Improved new console command 2021-03-10 11:28:04 +01:00
064237904a Improved console 2021-03-10 10:49:40 +01:00
221017ae7e Improved console 2021-03-10 09:04:53 +01:00
59206dbc32 Improved spinner 2021-03-10 08:17:52 +01:00
f4ca76d844 Improved template handling 2021-03-10 08:09:56 +01:00
31515d2c3a Fixed help command & edited project structure doc 2021-03-09 22:31:54 +01:00
314049f22b Added command new console 2021-03-09 22:29:14 +01:00
87c6899949 Improved publishing 2021-03-09 22:26:54 +01:00
e8afff0e48 Improved publishing 2021-03-09 21:07:47 +01:00
11399d7ea7 Removed print 2021-03-09 18:30:05 +01:00
35752b2ae0 Removed print 2021-03-09 18:29:59 +01:00
ff0830c128 Bugfixes in configuration console argument handling 2021-03-09 18:27:16 +01:00
c574a44a26 Bugfixes in configuration console argument handling 2021-03-09 18:27:09 +01:00
b178e884f6 Bugfixes 2021-03-09 14:35:45 +01:00
1097477a10 Removed .idea 2021-03-08 22:33:24 +01:00
88142ead23 Revert "Readded workspace"
This reverts commit 243e48e8
2021-03-08 22:32:56 +01:00
243e48e8a9 Readded workspace 2021-03-08 22:32:11 +01:00
22858c8d2c Removed workspace 2021-03-08 22:31:20 +01:00
3740996db3 Bugfixes 2021-03-08 22:29:28 +01:00
82ea419088 Added imports 2021-03-08 22:19:36 +01:00
2ffb101442 Bugfixes 2021-03-08 22:14:37 +01:00
8f46e82905 Improved gitignore 2021-03-08 22:02:59 +01:00
e8538d751b Added modules imports 2021-03-08 22:02:16 +01:00
964a44a393 Bugfixes 2021-03-08 21:52:11 +01:00
1bbbb2d8e1 Changed configuration to upper case 2021-03-08 20:49:26 +01:00
f931ccb7dd Changed copyright date 2021-03-08 20:40:54 +01:00
3e33a8a773 Bugfixes in build process 2021-03-08 20:32:09 +01:00
2d7496dd6e Bugfixes in build process 2021-03-08 20:29:54 +01:00
f9826e28a5 Added publish logic 2021-03-08 20:29:08 +01:00
5f1b56f30b Added argument handling to spinner 2021-03-05 17:14:19 +01:00
562bb81379 Fixed output when console is spinning 2021-03-05 17:09:12 +01:00
0a630f9316 Bugfix 2021-03-05 16:53:26 +01:00
49f461037a Bugfix 2021-03-05 16:43:59 +01:00
9a0d36ca86 Improved publisher 2021-03-05 16:31:27 +01:00
1dfae27d88 Improved spinner 2021-03-05 16:27:38 +01:00
570ede237e Added spinner 2021-03-05 15:55:14 +01:00
236597848a Improved workspace 2021-03-04 23:26:35 +01:00
d3e1d3c368 Readded workspace.xml 2021-03-04 23:24:48 +01:00
b7f628979e Ran build command 2021-03-04 21:57:30 +01:00
83d0451591 Added build command 2021-03-04 21:57:18 +01:00
700bf9c937 Improved command handling 2021-03-04 19:06:53 +01:00
2c43e55f77 Improved command handling 2021-03-04 19:06:37 +01:00
69211cc9e8 Improved dependency injection 2021-03-04 19:06:16 +01:00
6452226b50 Improved configuration 2021-03-04 18:12:51 +01:00
01ef965180 Improved service providing 2021-03-04 17:55:15 +01:00
956a107d38 Improved commands 2021-03-04 17:36:02 +01:00
266f9945f4 Improved ServiceProvider 2021-03-04 17:29:04 +01:00
3b6121c9cf Removed workspace from git 2021-03-04 07:19:28 +01:00
ce1f057f71 Improved command handler 2021-03-04 07:18:36 +01:00
226123a81f Improved application 2021-03-04 07:15:48 +01:00
aa4931fd18 Improved application 2021-03-04 07:09:08 +01:00
164c639d29 Added cpl.json 2021-03-04 06:55:37 +01:00
960607a60d Improved application host 2021-03-04 06:53:38 +01:00
2c2c1ede21 Smaller bugfixes 2021-03-04 06:42:40 +01:00
8872d1bfe7 Improved cli 2021-03-03 19:48:55 +01:00
d5975ea7ba Improved help command 2021-03-03 19:47:55 +01:00
2174cf3701 Added version and help cli command 2021-03-03 19:37:35 +01:00
7a2f4584b0 Improved application 2021-03-03 19:37:18 +01:00
7223651322 Improved gitignore 2021-03-03 18:38:34 +01:00
9581283d7d Added cli application 2021-03-03 18:37:54 +01:00
b32f846fcf Improved configuration 2021-03-03 18:37:39 +01:00
ed0b761502 Removed old sources 2021-03-03 10:51:33 +01:00
68c136a16f Refactored code 2021-03-03 10:47:52 +01:00
325 changed files with 7994 additions and 5237 deletions

4
.gitignore vendored
View File

@@ -37,7 +37,7 @@ MANIFEST
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
# Unit model / coverage reports
htmlcov/
.tox/
.nox/
@@ -130,4 +130,4 @@ dmypy.json
# IDE
.vscode/
.idea/

View File

@@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="sven" />
</component>

View File

@@ -1,17 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="termcolor" />
<item index="1" class="java.lang.String" itemvalue="discord" />
<item index="2" class="java.lang.String" itemvalue="mysql-connector" />
<item index="3" class="java.lang.String" itemvalue="flask" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/publish_templates/all_template.txt" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/sh_common_py_lib.iml" filepath="$PROJECT_DIR$/.idea/sh_common_py_lib.iml" />
</modules>
</component>
</project>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/src/tests/logs" />
<excludeFolder url="file://$MODULE_DIR$/src/tests_dev/logs" />
<excludeFolder url="file://$MODULE_DIR$/src/sh_edraft.egg-info" />
<excludeFolder url="file://$MODULE_DIR$/src/dist" />
<excludeFolder url="file://$MODULE_DIR$/src/build" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/src/tests/old" />
<excludeFolder url="file://$MODULE_DIR$/.vscode" />
<excludeFolder url="file://$MODULE_DIR$/build_test" />
</content>
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +0,0 @@
<changelist name="Uncommitted_changes_before_Update_at_26_11_2020_10_36_[Default_Changelist]" date="1606383362450" recycled="false" toDelete="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Update_at_26_11_2020_10_36_[Default_Changelist]/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Update at 26.11.2020 10:36 [Default Changelist]" />
</changelist>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

868
.idea/workspace.xml generated
View File

@@ -1,868 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="BranchesTreeState">
<expand>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
</path>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
</path>
<path>
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
<item name="GROUP_NODE:origin" type="e8cecc67:BranchNodeDescriptor" />
</path>
</expand>
<select />
</component>
<component name="ChangeListManager">
<list default="true" id="7e2256bc-a6b8-4880-83a6-8b0e3372d0a4" name="Default Changelist" comment="Removed unused packages">
<change afterPath="$PROJECT_DIR$/src/tests/publish_test/__init__.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/tests/publish_test/publisher_test.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/tests/setup.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/sh_common_py_lib.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/sh_common_py_lib.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/build/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/build/app.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/publish/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/publish/app.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/base/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/base/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/base/publisher_base.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/base/publisher_base.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/model/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/model/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/model/publish_settings_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/model/publish_settings_model.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/model/publish_settings_name.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/model/publish_settings_name.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/model/template.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/model/template.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/model/template_enum.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/model/template_enum.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/sh_edraft/publishing/publisher.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/sh_edraft/publish/publisher.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/appsettings.edrafts-lapi.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/appsettings.edrafts-lapi.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/appsettings.edrafts-pc.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/appsettings.edrafts-pc.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/build.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/build.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/service_test/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/service_test/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/time_test/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/time_test/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tests/utils_test/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/tests/utils_test/__init__.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
<option value="Python Unit Test" />
</list>
</option>
</component>
<component name="Git.Pull.Settings">
<option name="BRANCH" value="2020.12.6" />
</component>
<component name="Git.Rebase.Settings">
<option name="NEW_BASE" value="2020.12" />
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="2020.12" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
<option name="RESET_MODE" value="HARD" />
<option name="UPDATE_TYPE" value="REBASE" />
</component>
<component name="ProjectId" id="1kYeNqJzjIXigSYYCzg4D16FL1E" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/tests" />
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/tests" />
<recent name="$PROJECT_DIR$/src/tests_dev" />
<recent name="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/database" />
<recent name="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/publish" />
<recent name="$PROJECT_DIR$/src" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/tests" />
<recent name="$PROJECT_DIR$/src/tests/publish_test" />
<recent name="$PROJECT_DIR$/src/tests/old" />
<recent name="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/commands/build" />
<recent name="$PROJECT_DIR$/src" />
</key>
</component>
<component name="RunManager" selected="Python tests.Unittests">
<configuration name="cli build" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="build test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli database migration" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/tests_dev" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="db mig add InitialMigration" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli database update" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/tests_dev" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="database update" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli help" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="help" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new app" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new app ./Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new base" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new app ./Test/Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new class" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new class ./Test/Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new configmodel" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new configmodel ./Test/Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new enum" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new enum ./Test/Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli new service" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="new service ./Test/Test" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli publish" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="publish" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli version" type="PythonConfigurationType" factoryName="Python">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="version" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="cli" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/sh_edraft/cli/cpl_cli/cli.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="CPL_NAME" value="CPL_Dev_Test" />
<env name="PYTHON_ENVIRONMENT" value="development" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/tests_dev" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/tests_dev/main.py" />
<option name="PARAMETERS" value="--customer=sh-edraft.de" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="Unittests" type="tests" factoryName="Unittests" temporary="true" nameIsGenerated="true">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="CPL_ENVIRONMENT" value="testing" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/tests" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_pattern" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;discover -s $PROJECT_DIR$/src/tests -p \u0027*_test.py\u0027&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;CUSTOM&quot;" />
<method v="2" />
</configuration>
<configuration name="Unittests" type="tests" factoryName="Unittests" temporary="true" nameIsGenerated="true">
<module name="sh_common_py_lib" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="CPL_ENVIRONMENT" value="testing" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/tests" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="_new_pattern" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;discover -s $PROJECT_DIR$/src/tests -p \u0027*_test.py\u0027&quot;" />
<option name="_new_target" value="&quot;&quot;" />
<option name="_new_targetType" value="&quot;CUSTOM&quot;" />
<method v="2" />
</configuration>
<list>
<item itemvalue="Python.cli build" />
<item itemvalue="Python.cli database migration" />
<item itemvalue="Python.cli database update" />
<item itemvalue="Python.cli help" />
<item itemvalue="Python.cli new app" />
<item itemvalue="Python.cli new base" />
<item itemvalue="Python.cli new class" />
<item itemvalue="Python.cli new configmodel" />
<item itemvalue="Python.cli new enum" />
<item itemvalue="Python.cli new service" />
<item itemvalue="Python.cli publish" />
<item itemvalue="Python.cli version" />
<item itemvalue="Python.cli" />
<item itemvalue="Python.main" />
<item itemvalue="Python tests.Unittests" />
</list>
<recent_temporary>
<list>
<item itemvalue="Python tests.Unittests" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="7e2256bc-a6b8-4880-83a6-8b0e3372d0a4" name="Default Changelist" comment="" />
<created>1605881914521</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1605881914521</updated>
</task>
<task id="LOCAL-00031" summary="Improved database module structure">
<created>1607285697331</created>
<option name="number" value="00031" />
<option name="presentableId" value="LOCAL-00031" />
<option name="project" value="LOCAL" />
<updated>1607285697331</updated>
</task>
<task id="LOCAL-00032" summary="Improved database module structure">
<created>1607620265254</created>
<option name="number" value="00032" />
<option name="presentableId" value="LOCAL-00032" />
<option name="project" value="LOCAL" />
<updated>1607620265254</updated>
</task>
<task id="LOCAL-00033" summary="Changed DatabaseConnection">
<created>1607712129507</created>
<option name="number" value="00033" />
<option name="presentableId" value="LOCAL-00033" />
<option name="project" value="LOCAL" />
<updated>1607712129507</updated>
</task>
<task id="LOCAL-00034" summary="Improved session reference">
<created>1607971885250</created>
<option name="number" value="00034" />
<option name="presentableId" value="LOCAL-00034" />
<option name="project" value="LOCAL" />
<updated>1607971885250</updated>
</task>
<task id="LOCAL-00035" summary="Added console module">
<created>1607976456889</created>
<option name="number" value="00035" />
<option name="presentableId" value="LOCAL-00035" />
<option name="project" value="LOCAL" />
<updated>1607976456889</updated>
</task>
<task id="LOCAL-00036" summary="Added console module">
<created>1607976626173</created>
<option name="number" value="00036" />
<option name="presentableId" value="LOCAL-00036" />
<option name="project" value="LOCAL" />
<updated>1607976626173</updated>
</task>
<task id="LOCAL-00037" summary="Improved console">
<created>1608037411172</created>
<option name="number" value="00037" />
<option name="presentableId" value="LOCAL-00037" />
<option name="project" value="LOCAL" />
<updated>1608037411172</updated>
</task>
<task id="LOCAL-00038" summary="Improved console">
<created>1608047595029</created>
<option name="number" value="00038" />
<option name="presentableId" value="LOCAL-00038" />
<option name="project" value="LOCAL" />
<updated>1608047595029</updated>
</task>
<task id="LOCAL-00039" summary="Improved ApplicationHost">
<created>1608047655667</created>
<option name="number" value="00039" />
<option name="presentableId" value="LOCAL-00039" />
<option name="project" value="LOCAL" />
<updated>1608047655667</updated>
</task>
<task id="LOCAL-00040" summary="Improved publisher">
<created>1608048544558</created>
<option name="number" value="00040" />
<option name="presentableId" value="LOCAL-00040" />
<option name="project" value="LOCAL" />
<updated>1608048544558</updated>
</task>
<task id="LOCAL-00041" summary="Improved publisher">
<created>1608049672925</created>
<option name="number" value="00041" />
<option name="presentableId" value="LOCAL-00041" />
<option name="project" value="LOCAL" />
<updated>1608049672925</updated>
</task>
<task id="LOCAL-00042" summary="Published for 2020.12.9">
<created>1608049772153</created>
<option name="number" value="00042" />
<option name="presentableId" value="LOCAL-00042" />
<option name="project" value="LOCAL" />
<updated>1608049772153</updated>
</task>
<task id="LOCAL-00043" summary="Publish improvements for pip install">
<created>1608059323858</created>
<option name="number" value="00043" />
<option name="presentableId" value="LOCAL-00043" />
<option name="project" value="LOCAL" />
<updated>1608059323858</updated>
</task>
<task id="LOCAL-00044" summary="Improved setup.py">
<created>1608059501153</created>
<option name="number" value="00044" />
<option name="presentableId" value="LOCAL-00044" />
<option name="project" value="LOCAL" />
<updated>1608059501153</updated>
</task>
<task id="LOCAL-00045" summary="Added first cli commands">
<created>1608066984496</created>
<option name="number" value="00045" />
<option name="presentableId" value="LOCAL-00045" />
<option name="project" value="LOCAL" />
<updated>1608066984496</updated>
</task>
<task id="LOCAL-00046" summary="Bugfixes and improved setup logic">
<created>1608070231599</created>
<option name="number" value="00046" />
<option name="presentableId" value="LOCAL-00046" />
<option name="project" value="LOCAL" />
<updated>1608070231599</updated>
</task>
<task id="LOCAL-00047" summary="Improved cli">
<created>1608122289003</created>
<option name="number" value="00047" />
<option name="presentableId" value="LOCAL-00047" />
<option name="project" value="LOCAL" />
<updated>1608122289003</updated>
</task>
<task id="LOCAL-00048" summary="Added version command to cli">
<created>1608125743068</created>
<option name="number" value="00048" />
<option name="presentableId" value="LOCAL-00048" />
<option name="project" value="LOCAL" />
<updated>1608125743068</updated>
</task>
<task id="LOCAL-00049" summary="Improved version command &amp; improved help command">
<created>1608127752645</created>
<option name="number" value="00049" />
<option name="presentableId" value="LOCAL-00049" />
<option name="project" value="LOCAL" />
<updated>1608127752645</updated>
</task>
<task id="LOCAL-00050" summary="Improved cli error handling">
<created>1608127998033</created>
<option name="number" value="00050" />
<option name="presentableId" value="LOCAL-00050" />
<option name="project" value="LOCAL" />
<updated>1608127998034</updated>
</task>
<task id="LOCAL-00051" summary="Removed module template">
<created>1608128026922</created>
<option name="number" value="00051" />
<option name="presentableId" value="LOCAL-00051" />
<option name="project" value="LOCAL" />
<updated>1608128026922</updated>
</task>
<task id="LOCAL-00052" summary="Added build command to cli">
<created>1608133651981</created>
<option name="number" value="00052" />
<option name="presentableId" value="LOCAL-00052" />
<option name="project" value="LOCAL" />
<updated>1608133651982</updated>
</task>
<task id="LOCAL-00053" summary="Improved publish process">
<created>1608136663213</created>
<option name="number" value="00053" />
<option name="presentableId" value="LOCAL-00053" />
<option name="project" value="LOCAL" />
<updated>1608136663213</updated>
</task>
<task id="LOCAL-00054" summary="Improved publish process">
<created>1608137669182</created>
<option name="number" value="00054" />
<option name="presentableId" value="LOCAL-00054" />
<option name="project" value="LOCAL" />
<updated>1608137669182</updated>
</task>
<task id="LOCAL-00055" summary="Added publish command to cli">
<created>1608138851088</created>
<option name="number" value="00055" />
<option name="presentableId" value="LOCAL-00055" />
<option name="project" value="LOCAL" />
<updated>1608138851088</updated>
</task>
<task id="LOCAL-00056" summary="Added imports to cli module">
<created>1608138986801</created>
<option name="number" value="00056" />
<option name="presentableId" value="LOCAL-00056" />
<option name="project" value="LOCAL" />
<updated>1608138986801</updated>
</task>
<task id="LOCAL-00057" summary="Improved help command">
<created>1608139067953</created>
<option name="number" value="00057" />
<option name="presentableId" value="LOCAL-00057" />
<option name="project" value="LOCAL" />
<updated>1608139067953</updated>
</task>
<task id="LOCAL-00058" summary="Changed deps">
<created>1608139239527</created>
<option name="number" value="00058" />
<option name="presentableId" value="LOCAL-00058" />
<option name="project" value="LOCAL" />
<updated>1608139239527</updated>
</task>
<task id="LOCAL-00059" summary="Bugfixes">
<created>1608139414722</created>
<option name="number" value="00059" />
<option name="presentableId" value="LOCAL-00059" />
<option name="project" value="LOCAL" />
<updated>1608139414722</updated>
</task>
<task id="LOCAL-00060" summary="Bugfixes">
<created>1608139502304</created>
<option name="number" value="00060" />
<option name="presentableId" value="LOCAL-00060" />
<option name="project" value="LOCAL" />
<updated>1608139502304</updated>
</task>
<task id="LOCAL-00061" summary="Removed publisher.py">
<created>1608139612476</created>
<option name="number" value="00061" />
<option name="presentableId" value="LOCAL-00061" />
<option name="project" value="LOCAL" />
<updated>1608139612476</updated>
</task>
<task id="LOCAL-00062" summary="Added first steps of database command">
<created>1608383809354</created>
<option name="number" value="00062" />
<option name="presentableId" value="LOCAL-00062" />
<option name="project" value="LOCAL" />
<updated>1608383809355</updated>
</task>
<task id="LOCAL-00063" summary="Improved error handling">
<created>1608383870849</created>
<option name="number" value="00063" />
<option name="presentableId" value="LOCAL-00063" />
<option name="project" value="LOCAL" />
<updated>1608383870849</updated>
</task>
<task id="LOCAL-00064" summary="Improved service providing">
<created>1608472168532</created>
<option name="number" value="00064" />
<option name="presentableId" value="LOCAL-00064" />
<option name="project" value="LOCAL" />
<updated>1608472168532</updated>
</task>
<task id="LOCAL-00065" summary="Added email client">
<created>1608472183294</created>
<option name="number" value="00065" />
<option name="presentableId" value="LOCAL-00065" />
<option name="project" value="LOCAL" />
<updated>1608472183294</updated>
</task>
<task id="LOCAL-00066" summary="Published">
<created>1608474022759</created>
<option name="number" value="00066" />
<option name="presentableId" value="LOCAL-00066" />
<option name="project" value="LOCAL" />
<updated>1608474022759</updated>
</task>
<task id="LOCAL-00067" summary="Improved publisher">
<created>1608474032244</created>
<option name="number" value="00067" />
<option name="presentableId" value="LOCAL-00067" />
<option name="project" value="LOCAL" />
<updated>1608474032244</updated>
</task>
<task id="LOCAL-00068" summary="Added imports">
<created>1608474289859</created>
<option name="number" value="00068" />
<option name="presentableId" value="LOCAL-00068" />
<option name="project" value="LOCAL" />
<updated>1608474289859</updated>
</task>
<task id="LOCAL-00069" summary="Removed build form gitignore, added build command">
<created>1608813096023</created>
<option name="number" value="00069" />
<option name="presentableId" value="LOCAL-00069" />
<option name="project" value="LOCAL" />
<updated>1608813096023</updated>
</task>
<task id="LOCAL-00070" summary="Added init file for build command package">
<created>1608813152266</created>
<option name="number" value="00070" />
<option name="presentableId" value="LOCAL-00070" />
<option name="project" value="LOCAL" />
<updated>1608813152266</updated>
</task>
<task id="LOCAL-00071" summary="Added pycharm files to gitignore">
<created>1608822597300</created>
<option name="number" value="00071" />
<option name="presentableId" value="LOCAL-00071" />
<option name="project" value="LOCAL" />
<updated>1608822597311</updated>
</task>
<task id="LOCAL-00072" summary="Added more development tests">
<created>1608823061488</created>
<option name="number" value="00072" />
<option name="presentableId" value="LOCAL-00072" />
<option name="project" value="LOCAL" />
<updated>1608823061489</updated>
</task>
<task id="LOCAL-00073" summary="Added new tester and CredentialManager TestCase">
<created>1608894745271</created>
<option name="number" value="00073" />
<option name="presentableId" value="LOCAL-00073" />
<option name="project" value="LOCAL" />
<updated>1608894745274</updated>
</task>
<task id="LOCAL-00074" summary="Bugfix">
<created>1608896199033</created>
<option name="number" value="00074" />
<option name="presentableId" value="LOCAL-00074" />
<option name="project" value="LOCAL" />
<updated>1608896199034</updated>
</task>
<task id="LOCAL-00075" summary="Added TimeFormatSettings Test">
<created>1608896235005</created>
<option name="number" value="00075" />
<option name="presentableId" value="LOCAL-00075" />
<option name="project" value="LOCAL" />
<updated>1608896235006</updated>
</task>
<task id="LOCAL-00076" summary="Bugfixes">
<created>1608901608928</created>
<option name="number" value="00076" />
<option name="presentableId" value="LOCAL-00076" />
<option name="project" value="LOCAL" />
<updated>1608901608930</updated>
</task>
<task id="LOCAL-00077" summary="Improved tests">
<created>1608921175852</created>
<option name="number" value="00077" />
<option name="presentableId" value="LOCAL-00077" />
<option name="project" value="LOCAL" />
<updated>1608921175853</updated>
</task>
<task id="LOCAL-00078" summary="Bugfixes and improved tests">
<created>1608986131753</created>
<option name="number" value="00078" />
<option name="presentableId" value="LOCAL-00078" />
<option name="project" value="LOCAL" />
<updated>1608986131755</updated>
</task>
<task id="LOCAL-00079" summary="Removed unused packages">
<created>1611431001647</created>
<option name="number" value="00079" />
<option name="presentableId" value="LOCAL-00079" />
<option name="project" value="LOCAL" />
<updated>1611431001649</updated>
</task>
<option name="localTasksCounter" value="80" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
<option name="oldMeFiltersMigrated" value="true" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Added build command to cli" />
<MESSAGE value="Improved publish process" />
<MESSAGE value="Added publish command to cli" />
<MESSAGE value="Added imports to cli module" />
<MESSAGE value="Improved help command" />
<MESSAGE value="Changed deps" />
<MESSAGE value="Removed publisher.py" />
<MESSAGE value="Added first steps of database command" />
<MESSAGE value="Improved error handling" />
<MESSAGE value="Improved service providing" />
<MESSAGE value="Added email client" />
<MESSAGE value="Published" />
<MESSAGE value="Improved publisher" />
<MESSAGE value="Added imports" />
<MESSAGE value="Removed build form gitignore, added build command" />
<MESSAGE value="Added init file for build command package" />
<MESSAGE value="Added pycharm files to gitignore" />
<MESSAGE value="Added more development tests" />
<MESSAGE value="Added new tester and CredentialManager TestCase" />
<MESSAGE value="Bugfix" />
<MESSAGE value="Added TimeFormatSettings Test" />
<MESSAGE value="Bugfixes" />
<MESSAGE value="Improved tests" />
<MESSAGE value="Bugfixes and improved tests" />
<MESSAGE value="Removed unused packages" />
<option name="LAST_COMMIT_MESSAGE" value="Removed unused packages" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/src/tests/publish_test/publisher_test.py</url>
<line>50</line>
<option name="timeStamp" value="3" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

101
README.md
View File

@@ -1,2 +1,101 @@
# 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="#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>
<!-- 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 sh_cpl --extra-index-url https://pip.sh-edraft.de
```
Create workspace:
```sh
cpl new <console|library> <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_common_py_lib/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_common_py_lib)
<!-- 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_common_py_lib
[quickstart]: https://git.sh-edraft.de/sh-edraft.de/sh_common_py_lib/wiki/quickstart
[contributing]: https://git.sh-edraft.de/sh-edraft.de/sh_common_py_lib/wiki/contributing
[license]: LICENSE

57
cpl.json Normal file
View File

@@ -0,0 +1,57 @@
{
"ProjectSettings": {
"Name": "sh_cpl",
"Version": {
"Major": "2021",
"Minor": "04",
"Micro": "0"
},
"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.3",
"setuptools==54.2.0",
"tabulate==0.8.9",
"termcolor==1.1.0",
"watchdog==2.0.2",
"wheel==0.36.2"
],
"PythonVersion": ">=3.8",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "src",
"OutputPath": "dist",
"Main": "cpl_cli.main",
"EntryPoint": "cpl",
"IncludePackageData": true,
"Included": [
"*/templates"
],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {
"cpl_cli": [
"*.json"
]
}
}
}

View File

@@ -1,9 +0,0 @@
prefix: cpl
commands:
new:
app
base
class
configmodel
enum
service

19
docs/cli.txt Normal file
View File

@@ -0,0 +1,19 @@
prefix: cpl
commands:
build
generate:
abc | a
class | c
configmodel | cm
enum | e
service | s
help
new
console
start
publish
update
version

11
docs/pip.txt Normal file
View File

@@ -0,0 +1,11 @@
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

View File

@@ -1,10 +0,0 @@
- sh_edraft
- common # Contains Interfaces and models for the hole library
- interface
- model
- configuration # Contains classes for app configuration by JSON, ENV vars and arguments
- discord # Contains classes for better use of discord.py
- logging # Contains classes for logging
- mailing # Contains classes for mailing
- messenger # Contains classes for sh_messenger_server client
- service # Contains classes to provide and use the services defined in this library

View File

@@ -1,2 +0,0 @@
- create logger
- use logger in publisher

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
"""
$Name $Description
~~~~~~~~~~~~~~~~~~~
$LongDescription
:copyright: (c) $CopyrightDate $CopyrightName
:license: $LicenseName$LicenseDescription
"""
__title__ = '$Title'
__author__ = '$Author'
__license__ = '$LicenseName'
__copyright__ = 'Copyright (c) $CopyrightDate $CopyrightName'
__version__ = '$Version'
from collections import namedtuple
$Imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major=$Major, minor=$Minor, micro=$Micro)

View File

@@ -1,29 +0,0 @@
aiohttp==3.6.3
async-timeout==3.0.1
attrs==20.3.0
certifi==2020.11.8
chardet==3.0.4
click==7.1.2
dateutils==0.6.12
discord==1.0.1
discord.py==1.5.1
Flask==1.1.2
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
keyboard==0.13.5
MarkupSafe==1.1.1
multidict==4.7.6
mysql-connector==2.2.9
overloading==0.5.0
python-dateutil==2.8.1
pytz==2020.4
six==1.15.0
SQLAlchemy==1.3.20
termcolor==1.1.0
urllib3==1.26.2
Werkzeug==1.0.1
yarl==1.5.1
setuptools~=49.2.1
pyfiglet~=0.8.post1
tabulate~=0.8.7

View File

@@ -1,2 +0,0 @@
include ../ README
recursive-include sh_edraft *.txt

View File

@@ -1,66 +0,0 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "../build/logs/",
"Filename": "log_$start_time.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "TRACE"
},
"PublishSettings": {
"SourcePath": "./",
"DistPath": "../build/dist",
"Templates": [
{
"TemplatePath": "../publish_templates/all_template.txt",
"Name": "all",
"Description": "",
"LongDescription": "",
"CopyrightDate": "2020",
"CopyrightName": "sh-edraft.de",
"LicenseName": "MIT",
"LicenseDescription": ", see LICENSE for more details.",
"Title": "",
"Author": "Sven Heidemann",
"Version": {
"Major": 2020,
"Minor": 12,
"Micro": 10
}
},
{
"TemplatePath": "../publish_templates/all_template.txt",
"Name": "sh_edraft",
"Description": "common python library",
"LongDescription": "Library to share common classes and models used at sh-edraft.de",
"CopyrightDate": "2020",
"CopyrightName": "sh-edraft.de",
"LicenseName": "MIT",
"LicenseDescription": ", see LICENSE for more details.",
"Title": "",
"Author": "Sven Heidemann",
"Version": {
"Major": 2020,
"Minor": 12,
"Micro": 10
}
}
],
"IncludedFiles": [
"./MANIFEST.in",
"../LICENSE",
"../README.md",
"../requirements.txt",
"sh_edraft/cli/cpl_cli/templates"
],
"ExcludedFiles": [
"./tests",
"./tests_dev"
],
"TemplateEnding": "_template.txt"
}
}

25
src/cpl/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'sh_cpl.cpl'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.application'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .application_abc import ApplicationABC
from .application_builder import ApplicationBuilder
from .application_builder_abc import ApplicationBuilderABC
from .startup_abc import StartupABC
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,46 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.console.console import Console
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.environment import ApplicationEnvironmentABC
class ApplicationABC(ABC):
@abstractmethod
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
"""
ABC of application
"""
self._configuration: Optional[ConfigurationABC] = config
self._environment: Optional[ApplicationEnvironmentABC] = self._configuration.environment
self._services: Optional[ServiceProviderABC] = services
def run(self):
"""
Entry point
:return:
"""
try:
self.configure()
self.main()
except KeyboardInterrupt:
Console.close()
@abstractmethod
def configure(self):
"""
Prepare the application
:return:
"""
pass
@abstractmethod
def main(self):
"""
Custom entry point
:return:
"""
pass

View File

@@ -0,0 +1,41 @@
from typing import Type, Optional
from cpl.application.application_abc import ApplicationABC
from cpl.application.application_builder_abc import ApplicationBuilderABC
from cpl.application.startup_abc import StartupABC
from cpl.configuration.configuration import Configuration
from cpl.dependency_injection.service_collection import ServiceCollection
class ApplicationBuilder(ApplicationBuilderABC):
def __init__(self, app: Type[ApplicationABC]):
"""
Builder class for application
"""
ApplicationBuilderABC.__init__(self)
self._app = app
self._startup: Optional[StartupABC] = None
self._configuration = Configuration()
self._environment = self._configuration.environment
self._services = ServiceCollection(self._configuration)
def use_startup(self, startup: Type[StartupABC]):
"""
Sets the used startup class
:param startup:
:return:
"""
self._startup = startup(self._configuration, self._services)
def build(self) -> ApplicationABC:
"""
Creates application host and runtime
:return:
"""
if self._startup is not None:
self._startup.configure_configuration()
self._startup.configure_services()
return self._app(self._configuration, self._services.build_service_provider())

View File

@@ -0,0 +1,30 @@
from abc import ABC, abstractmethod
from typing import Type
from cpl.application.application_abc import ApplicationABC
from cpl.application.startup_abc import StartupABC
class ApplicationBuilderABC(ABC):
def __init__(self, *args):
"""
ABC of application builder
"""
@abstractmethod
def use_startup(self, startup: Type[StartupABC]):
"""
Sets the used startup class
:param startup:
:return:
"""
pass
@abstractmethod
def build(self) -> ApplicationABC:
"""
Creates application host and runtime
:return:
"""
pass

View File

@@ -0,0 +1,29 @@
from abc import ABC, abstractmethod
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
class StartupABC(ABC):
@abstractmethod
def __init__(self, *args):
"""
ABC for a startup class
"""
@abstractmethod
def configure_configuration(self) -> ConfigurationABC:
"""
Creates configuration of application
:return: configuration
"""
pass
@abstractmethod
def configure_services(self) -> ServiceProviderABC:
"""
Creates service provider
:return: service provider
"""
pass

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.configuration'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .configuration import Configuration
from .configuration_abc import ConfigurationABC
from .configuration_model_abc import ConfigurationModelABC
from .configuration_variable_name_enum import ConfigurationVariableNameEnum
from .console_argument import ConsoleArgument
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,357 @@
import json
import os
import sys
from collections import Callable
from typing import Union, Type, Optional
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.configuration.configuration_variable_name_enum import ConfigurationVariableNameEnum
from cpl.configuration.console_argument import ConsoleArgument
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.environment.application_environment import ApplicationEnvironment
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.environment.environment_name_enum import EnvironmentNameEnum
class Configuration(ConfigurationABC):
def __init__(self):
"""
Representation of configuration
"""
ConfigurationABC.__init__(self)
self._application_environment = ApplicationEnvironment()
self._config: dict[Union[type, str], Union[ConfigurationModelABC, str]] = {}
self._argument_types: list[ConsoleArgument] = []
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
@staticmethod
def _print_info(name: str, message: str):
"""
Prints an info message
:param name:
:param message:
:return:
"""
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(f'[{name}] {message}')
Console.set_foreground_color(ForegroundColorEnum.default)
@staticmethod
def _print_warn(name: str, message: str):
"""
Prints a warning
:param name:
:param message:
:return:
"""
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(f'[{name}] {message}')
Console.set_foreground_color(ForegroundColorEnum.default)
@staticmethod
def _print_error(name: str, message: str):
"""
Prints an error
:param name:
:param message:
:return:
"""
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[{name}] {message}')
Console.set_foreground_color(ForegroundColorEnum.default)
def _set_variable(self, name: str, value: str):
"""
Sets variable to given value
:param name:
:param value:
:return:
"""
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 _validate_argument_child(self, argument: str, argument_type: ConsoleArgument,
next_arguments: Optional[list[str]]) -> bool:
"""
Validates the child arguments of argument
:param argument:
:param argument_type:
:param next_arguments:
:return:
"""
if argument_type.console_arguments is not None and len(argument_type.console_arguments) > 0:
found = False
for child_argument_type in argument_type.console_arguments:
found = self._validate_argument_by_argument_type(argument, child_argument_type, next_arguments)
if found and child_argument_type.name not in self._additional_arguments:
self._additional_arguments.append(child_argument_type.name)
if not found:
raise Exception(f'Invalid argument: {argument}')
return found
return True
def _validate_argument_by_argument_type(self, argument: str, argument_type: ConsoleArgument,
next_arguments: list[str] = None) -> bool:
"""
Validate argument by argument type
:param argument:
:param argument_type:
:param next_arguments:
:return:
"""
argument_name = ''
value = ''
result = False
if argument_type.value_token != '' and argument_type.value_token in argument:
# ?new=value
found = False
for alias in argument_type.aliases:
if alias in argument:
found = True
if argument_type.name not in argument_name and not found:
return False
if argument_type.is_value_token_optional is not None and argument_type.is_value_token_optional:
if argument_type.name not in self._additional_arguments:
self._additional_arguments.append(argument_type.name)
result = True
if argument_type.token != '' and argument.startswith(argument_type.token):
# --new=value
argument_name = argument.split(argument_type.token)[1].split(argument_type.value_token)[0]
else:
# new=value
argument_name = argument.split(argument_type.token)[1]
result = True
if argument_type.is_value_token_optional is True:
is_valid = False
name_list = argument.split(argument_type.token)
if len(name_list) > 1:
value_list = name_list[1].split(argument_type.value_token)
if len(value_list) > 1:
is_valid = True
value = argument.split(argument_type.token)[1].split(argument_type.value_token)[1]
if not is_valid:
if argument_type.name not in self._additional_arguments:
self._additional_arguments.append(argument_type.name)
result = True
else:
value = argument.split(argument_type.token)[1].split(argument_type.value_token)[1]
if argument_name != argument_type.name and argument_name not in argument_type.aliases:
return False
self._set_variable(argument_type.name, value)
result = True
elif argument_type.value_token == ' ':
# ?new value
found = False
for alias in argument_type.aliases:
if alias in argument:
found = True
if argument_type.name not in argument and not found:
return False
if (next_arguments is None or len(next_arguments) == 0) and \
argument_type.is_value_token_optional is not True:
raise Exception(f'Invalid argument: {argument}')
if (next_arguments is None or len(next_arguments) == 0) and argument_type.is_value_token_optional is True:
value = ''
else:
value = next_arguments[0]
self._handled_args.append(value)
if argument_type.token != '' and argument.startswith(argument_type.token):
# --new value
argument_name = argument.split(argument_type.token)[1]
else:
# new value
argument_name = argument
if argument_name != argument_type.name and argument_name not in argument_type.aliases:
return False
if value == '':
if argument_type.name not in self._additional_arguments:
self._additional_arguments.append(argument_type.name)
else:
self._set_variable(argument_type.name, value)
result = True
elif argument_type.name == argument or argument in argument_type.aliases:
# new
self._additional_arguments.append(argument_type.name)
result = True
if result:
self._handled_args.append(argument)
if next_arguments is not None and len(next_arguments) > 0:
next_args = []
if len(next_arguments) > 1:
next_args = next_arguments[1:]
result = self._validate_argument_child(next_arguments[0], argument_type, next_args)
return result
def add_environment_variables(self, prefix: str):
for variable in ConfigurationVariableNameEnum.to_list():
var_name = f'{prefix}{variable}'
if var_name in [key.upper() for key in os.environ.keys()]:
self._set_variable(variable, os.environ[var_name])
def add_console_argument(self, argument: ConsoleArgument):
self._argument_types.append(argument)
def add_console_arguments(self, error: bool = None):
for arg_name in ConfigurationVariableNameEnum.to_list():
self.add_console_argument(ConsoleArgument('--', str(arg_name).upper(), [str(arg_name).lower()], '='))
arg_list = sys.argv[1:]
for i in range(0, len(arg_list)):
argument = arg_list[i]
next_arguments = []
error_message = ''
if argument in self._handled_args:
break
if i + 1 < len(arg_list):
next_arguments = arg_list[i + 1:]
found = False
for argument_type in self._argument_types:
try:
found = self._validate_argument_by_argument_type(argument, argument_type, next_arguments)
if found:
break
except Exception as e:
error_message = e
if not found and error_message == '' and error is not False:
error_message = f'Invalid argument: {argument}'
if self._argument_error_function is not None:
self._argument_error_function(error_message)
else:
self._print_error(__name__, error_message)
exit()
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
path_root = self._application_environment.content_root_path
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(__name__, f'File not found: {file_path}')
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 or sub.__name__.replace('Settings', '') == key:
configuration = sub()
configuration.from_dict(value)
self.add_configuration(sub, configuration)
def _load_json_file(self, file: str, output: bool) -> dict:
"""
Reads the json file
:param file:
:param output:
:return:
"""
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(__name__, f'Loaded config file: {file}')
return json_cfg
except Exception as e:
self._print_error(__name__, f'Cannot load config file: {file}! -> {e}')
return {}
def add_configuration(self, key_type: type, value: ConfigurationModelABC):
self._config[key_type] = value
def get_configuration(self, search_type: Union[str, Type[ConfigurationModelABC]]) -> Union[
str, Callable[ConfigurationModelABC]]:
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]

View File

@@ -0,0 +1,91 @@
from abc import abstractmethod, ABC
from collections import Callable
from typing import Type, Union, Optional
from cpl.configuration.console_argument import ConsoleArgument
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
class ConfigurationABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC of configuration
"""
pass
@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
@abstractmethod
def add_environment_variables(self, prefix: str):
"""
Reads the environment variables
:param prefix:
:return:
"""
pass
@abstractmethod
def add_console_argument(self, argument: ConsoleArgument):
"""
Adds console argument to known console arguments
:param argument:
:return:
"""
pass
@abstractmethod
def add_console_arguments(self, error: bool = None):
"""
Reads the console arguments
:param error: defines is invalid argument error will be shown or not
:return:
"""
pass
@abstractmethod
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
"""
Reads and saves settings from given json file
:param name:
:param optional:
:param output:
:param path:
:return:
"""
pass
@abstractmethod
def add_configuration(self, key_type: type, value: object):
"""
Add configuration object
:param key_type:
:param value:
:return:
"""
pass
@abstractmethod
def get_configuration(self, search_type: Union[str, Type[ConfigurationModelABC]]) -> Union[str, Callable[ConfigurationModelABC]]:
"""
Returns value in configuration by given type
:param search_type:
:return:
"""
pass

View File

@@ -0,0 +1,20 @@
from abc import ABC, abstractmethod
class ConfigurationModelABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC for settings representation
"""
pass
@abstractmethod
def from_dict(self, settings: dict):
"""
Converts attributes to dict
:param settings:
:return:
"""
pass

View File

@@ -1,7 +1,7 @@
from enum import Enum
class ConfigurationVariableName(Enum):
class ConfigurationVariableNameEnum(Enum):
environment = 'ENVIRONMENT'
name = 'NAME'
@@ -9,4 +9,4 @@ class ConfigurationVariableName(Enum):
@staticmethod
def to_list():
return [var.value for var in ConfigurationVariableName]
return [var.value for var in ConfigurationVariableNameEnum]

View File

@@ -0,0 +1,49 @@
class ConsoleArgument:
def __init__(self,
token: str,
name: str,
aliases: list[str],
value_token: str,
is_value_token_optional: bool = None,
console_arguments: list['ConsoleArgument'] = None
):
"""
Representation of an console argument
:param token:
:param name:
:param aliases:
:param value_token:
:param is_value_token_optional:
:param console_arguments:
"""
self._token = token
self._name = name
self._aliases = aliases
self._value_token = value_token
self._is_value_token_optional = is_value_token_optional
self._console_arguments = console_arguments
@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 value_token(self) -> str:
return self._value_token
@property
def is_value_token_optional(self) -> bool:
return self._is_value_token_optional
@property
def console_arguments(self) -> list['ConsoleArgument']:
return self._console_arguments

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.console'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
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
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -1,7 +1,7 @@
from enum import Enum
class BackgroundColor(Enum):
class BackgroundColorEnum(Enum):
default = 'on_default'
grey = 'on_grey'

528
src/cpl/console/console.py Normal file
View File

@@ -0,0 +1,528 @@
import os
import sys
import time
from collections import Callable
from typing import Union, Optional
import colorama
import pyfiglet
from pynput import keyboard
from pynput.keyboard import Key
from tabulate import tabulate
from termcolor import colored
from cpl.console.background_color_enum import BackgroundColorEnum
from cpl.console.console_call import ConsoleCall
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.console.spinner_thread import SpinnerThread
class Console:
"""
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]):
"""
Sets the background color
:param color:
:return:
"""
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]):
"""
Sets the foreground color
:param color:
:return:
"""
if type(color) is str:
cls._foreground_color = ForegroundColorEnum[color]
else:
cls._foreground_color = color
@classmethod
def reset_cursor_position(cls):
"""
Resets cursor position
:return:
"""
cls._x = None
cls._y = None
@classmethod
def set_cursor_position(cls, x: int, y: int):
"""
Sets cursor position
:param x:
:param y:
:return:
"""
cls._x = x
cls._y = y
"""
Useful protected methods
"""
@classmethod
def _output(cls, string: str, x: int = None, y: int = None, end: str = None):
"""
Prints given output with given format
:param string:
:param x:
:param y:
:param end:
:return:
"""
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):
"""
Shows the select menu
:return:
"""
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: Key):
"""
Event function when key press is detected
:param key:
:return:
"""
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 methods
"""
@classmethod
def banner(cls, string: str):
"""
Prints the string as a banner
:param string:
:return:
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.banner, string))
return
ascii_banner = pyfiglet.figlet_format(string)
cls.write_line(ascii_banner)
@classmethod
def color_reset(cls):
"""
Resets color
:return:
"""
cls._background_color = BackgroundColorEnum.default
cls._foreground_color = ForegroundColorEnum.default
@classmethod
def clear(cls):
"""
Clears the console
:return:
"""
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):
"""
Close the application
:return:
"""
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()
exit()
@classmethod
def disable(cls):
"""
Disable console interaction
:return:
"""
cls._disabled = True
@classmethod
def error(cls, string: str, tb: str = None):
"""
Prints an error with traceback
:param string:
:param tb:
:return:
"""
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):
"""
Enable console interaction
:return:
"""
cls._disabled = False
@classmethod
def read(cls, output: str = None) -> str:
"""
Read in line
:param output:
:return:
"""
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:
"""
Reads in next line
:param output:
:return:
"""
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]]):
"""
Prints a table with header and values
:param header:
:param values:
:return:
"""
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:
"""
Prints select menu
:param char:
:param message:
:param options:
:param header_foreground_color:
:param header_background_color:
:param option_foreground_color:
:param option_background_color:
:param cursor_foreground_color:
:param cursor_background_color:
:return: Selected option as 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()
with keyboard.Listener(
on_press=cls._select_menu_key_press, suppress=True) 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:
"""
Shows spinner and calls given function
When function has ended the spinner stops
:param message:
:param call:
:param args:
:param text_foreground_color:
:param spinner_foreground_color:
:param text_background_color:
:param spinner_background_color:
:param kwargs:
:return: 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 = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color)
spinner.start()
return_value = None
try:
return_value = call(*args, **kwargs)
except KeyboardInterrupt:
spinner.exit()
cls.close()
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)
time.sleep(0.1)
return return_value
@classmethod
def write(cls, *args, end=''):
"""
Prints in active line
:param args:
:param end:
:return:
"""
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):
"""
Prints at given position
:param x:
:param y:
:param args:
:return:
"""
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):
"""
Prints to new line
:param args:
:return:
"""
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):
"""
Prints new line at given position
:param x:
:param y:
:param args:
:return:
"""
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='')

View File

@@ -0,0 +1,21 @@
from collections import Callable
class ConsoleCall:
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
@property
def function(self):
return self._func
@property
def args(self):
return self._args

View File

@@ -1,7 +1,7 @@
from enum import Enum
class ForegroundColor(Enum):
class ForegroundColorEnum(Enum):
default = 'default'
grey = 'grey'

View File

@@ -0,0 +1,107 @@
import os
import sys
import threading
import time
from termcolor import colored
from cpl.console.background_color_enum import BackgroundColorEnum
from cpl.console.foreground_color_enum import ForegroundColorEnum
class SpinnerThread(threading.Thread):
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
self._foreground_color = foreground_color
self._background_color = background_color
self._is_spinning = True
self._exit = False
@staticmethod
def _spinner():
"""
Selects active spinner char
:return:
"""
while True:
for cursor in '|/-\\':
yield cursor
def _get_color_args(self) -> list[str]:
"""
Creates color arguments
:return:
"""
color_args = []
if self._foreground_color is not None:
color_args.append(str(self._foreground_color.value))
if self._background_color is not None:
color_args.append(str(self._background_color.value))
return color_args
def run(self) -> None:
"""
Entry point of thread, shows the spinner
:return:
"""
columns = 0
if sys.platform == 'win32':
columns = os.get_terminal_size().columns
else:
term_rows, term_columns = os.popen('stty size', 'r').read().split()
columns = int(term_columns)
end_msg = 'done'
end_msg_pos = columns - self._msg_len - len(end_msg)
if end_msg_pos > 0:
print(f'{"" : >{end_msg_pos}}', end='')
else:
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='')
else:
print(colored(f'{next(spinner): >{len(end_msg)}}', *self._get_color_args()), end='')
time.sleep(0.1)
back = ''
for i in range(0, len(end_msg)):
back += '\b'
print(back, end='')
sys.stdout.flush()
if not self._exit:
print(colored(end_msg, *self._get_color_args()), end='')
def stop_spinning(self):
"""
Stops the spinner
:return:
"""
self._is_spinning = False
time.sleep(0.1)
def exit(self):
"""
Stops the spinner
:return:
"""
self._is_spinning = False
self._exit = True
time.sleep(0.1)

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.database'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .database_model import DatabaseModel
from .database_settings import DatabaseSettings
from .database_settings_name_enum import DatabaseSettingsNameEnum
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.database.connection'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .database_connection import DatabaseConnection
from .database_connection_abc import DatabaseConnectionABC
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -3,16 +3,20 @@ from typing import Optional
from sqlalchemy import engine, create_engine
from sqlalchemy.orm import Session, sessionmaker
from sh_edraft.database.connection.base.database_connection_base import DatabaseConnectionBase
from sh_edraft.database.model.database_settings import DatabaseSettings
from sh_edraft.console.console import Console
from sh_edraft.console.model.foreground_color import ForegroundColor
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.database.connection.database_connection_abc import DatabaseConnectionABC
from cpl.database.database_settings import DatabaseSettings
class DatabaseConnection(DatabaseConnectionBase):
class DatabaseConnection(DatabaseConnectionABC):
def __init__(self, database_settings: DatabaseSettings):
DatabaseConnectionBase.__init__(self)
"""
Represents an connection to a database
:param database_settings:
"""
DatabaseConnectionABC.__init__(self)
self._db_settings = database_settings
@@ -32,6 +36,9 @@ class DatabaseConnection(DatabaseConnectionBase):
try:
self._engine = create_engine(connection_string)
if self._db_settings.auth_plugin is not None:
self._engine = create_engine(connection_string, connect_args={'auth_plugin': self._db_settings.auth_plugin})
if self._db_settings.encoding is not None:
self._engine.encoding = self._db_settings.encoding
@@ -45,12 +52,11 @@ class DatabaseConnection(DatabaseConnectionBase):
db_session = sessionmaker(bind=self._engine)
self._session = db_session()
Console.set_foreground_color(ForegroundColor.green)
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(f'[{__name__}] Connected to database')
Console.set_foreground_color(ForegroundColor.default)
Console.set_foreground_color(ForegroundColorEnum.default)
except Exception as e:
Console.set_foreground_color(ForegroundColor.red)
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[{__name__}] Database connection failed -> {e}')
Console.set_foreground_color(ForegroundColor.default)
Console.set_foreground_color(ForegroundColorEnum.default)
exit()

View File

@@ -4,7 +4,7 @@ from sqlalchemy import engine
from sqlalchemy.orm import Session
class DatabaseConnectionBase(ABC):
class DatabaseConnectionABC(ABC):
@abstractmethod
def __init__(self): pass
@@ -18,4 +18,10 @@ class DatabaseConnectionBase(ABC):
def session(self) -> Session: pass
@abstractmethod
def connect(self, connection_string: str): pass
def connect(self, connection_string: str):
"""
Connects to a database by connection string
:param connection_string:
:return:
"""
pass

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.database.context'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .database_context import DatabaseContext
from .database_context_abc import DatabaseContextABC
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,50 @@
from sqlalchemy import engine, Table
from sqlalchemy.orm import Session
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.database.connection.database_connection import DatabaseConnection
from cpl.database.connection.database_connection_abc import DatabaseConnectionABC
from cpl.database.context.database_context_abc import DatabaseContextABC
from cpl.database.database_settings import DatabaseSettings
from cpl.database.database_model import DatabaseModel
class DatabaseContext(DatabaseContextABC):
def __init__(self, database_settings: DatabaseSettings):
DatabaseContextABC.__init__(self)
self._db: DatabaseConnectionABC = DatabaseConnection(database_settings)
self._tables: list[Table] = []
@property
def engine(self) -> engine:
return self._db.engine
@property
def session(self) -> Session:
return self._db.session
def connect(self, connection_string: str):
self._db.connect(connection_string)
self._create_tables()
def save_changes(self):
self._db.session.commit()
def _create_tables(self):
try:
for subclass in DatabaseModel.__subclasses__():
self._tables.append(subclass.__table__)
DatabaseModel.metadata.drop_all(self._db.engine, self._tables)
DatabaseModel.metadata.create_all(self._db.engine, self._tables, checkfirst=True)
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(f'[{__name__}] Created tables')
Console.set_foreground_color(ForegroundColorEnum.default)
except Exception as e:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[{__name__}] Creating tables failed -> {e}')
Console.set_foreground_color(ForegroundColorEnum.default)
exit()

View File

@@ -0,0 +1,42 @@
from abc import abstractmethod, ABC
from sqlalchemy import engine
from sqlalchemy.orm import Session
class DatabaseContextABC(ABC):
@abstractmethod
def __init__(self, *args):
pass
@property
@abstractmethod
def engine(self) -> engine: pass
@property
@abstractmethod
def session(self) -> Session: pass
@abstractmethod
def connect(self, connection_string: str):
"""
Connects to a database with connection string
:param connection_string:
:return:
"""
pass
def save_changes(self):
"""
Saves changes of the database
"""
pass
@abstractmethod
def _create_tables(self):
"""
Create all tables for application from database model
:return:
"""
pass

View File

@@ -1,3 +1,3 @@
from sqlalchemy.ext.declarative import declarative_base
DBModel: declarative_base = declarative_base()
DatabaseModel: declarative_base = declarative_base()

View File

@@ -1,35 +1,44 @@
import traceback
from typing import Optional
from sh_edraft.configuration.base.configuration_model_base import ConfigurationModelBase
from sh_edraft.database.model.database_settings_name import DatabaseSettingsName
from sh_edraft.console.console import Console
from sh_edraft.console.model.foreground_color import ForegroundColor
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.database.database_settings_name_enum import DatabaseSettingsNameEnum
class DatabaseSettings(ConfigurationModelBase):
class DatabaseSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelBase.__init__(self)
ConfigurationModelABC.__init__(self)
self._auth_plugin: Optional[str] = None
self._connection_string: Optional[str] = None
self._credentials: Optional[str] = None
self._encoding: Optional[str] = None
self._case_sensitive: Optional[bool] = None
self._echo: Optional[bool] = None
@property
def auth_plugin(self) -> str:
return self._auth_plugin
@auth_plugin.setter
def auth_plugin(self, auth_plugin: str):
self._auth_plugin = auth_plugin
@property
def connection_string(self) -> str:
return self._connection_string
@connection_string.setter
def connection_string(self, connection_string: str):
self._connection_string = connection_string
@property
def credentials(self) -> str:
return self._credentials
@credentials.setter
def credentials(self, credentials: str):
self._credentials = credentials
@@ -60,19 +69,22 @@ class DatabaseSettings(ConfigurationModelBase):
def from_dict(self, settings: dict):
try:
self._connection_string = settings[DatabaseSettingsName.connection_string.value]
self._credentials = settings[DatabaseSettingsName.credentials.value]
self._connection_string = settings[DatabaseSettingsNameEnum.connection_string.value]
self._credentials = settings[DatabaseSettingsNameEnum.credentials.value]
if DatabaseSettingsName.encoding.value in settings:
self._encoding = settings[DatabaseSettingsName.encoding.value]
if DatabaseSettingsNameEnum.auth_plugin.value in settings:
self._auth_plugin = settings[DatabaseSettingsNameEnum.auth_plugin.value]
if DatabaseSettingsName.case_sensitive.value in settings:
self._case_sensitive = bool(settings[DatabaseSettingsName.case_sensitive.value])
if DatabaseSettingsNameEnum.encoding.value in settings:
self._encoding = settings[DatabaseSettingsNameEnum.encoding.value]
if DatabaseSettingsName.echo.value in settings:
self._echo = bool(settings[DatabaseSettingsName.echo.value])
if DatabaseSettingsNameEnum.case_sensitive.value in settings:
self._case_sensitive = bool(settings[DatabaseSettingsNameEnum.case_sensitive.value])
if DatabaseSettingsNameEnum.echo.value in settings:
self._echo = bool(settings[DatabaseSettingsNameEnum.echo.value])
except Exception as e:
Console.set_foreground_color(ForegroundColor.red)
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.write_line(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
Console.set_foreground_color(ForegroundColor.default)
Console.set_foreground_color(ForegroundColorEnum.default)

View File

@@ -1,10 +1,11 @@
from enum import Enum
class DatabaseSettingsName(Enum):
class DatabaseSettingsNameEnum(Enum):
connection_string = 'ConnectionString'
credentials = 'Credentials'
encoding = 'Encoding'
case_sensitive = 'CaseSensitive'
echo = 'Echo'
auth_plugin = 'AuthPlugin'

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.dependency_injection'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
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
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,70 @@
from typing import Union, Type, Callable, Optional
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.database.database_settings import DatabaseSettings
from cpl.database.context.database_context_abc import DatabaseContextABC
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.dependency_injection.service_collection_abc import ServiceCollectionABC
from cpl.dependency_injection.service_descriptor import ServiceDescriptor
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
from cpl.dependency_injection.service_provider import ServiceProvider
from cpl.logging.logger_service import Logger
from cpl.logging.logger_abc import LoggerABC
from cpl.utils.credential_manager import CredentialManager
class ServiceCollection(ServiceCollectionABC):
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):
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))
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
self._database_context = db_context_type(db_settings)
self._database_context.connect(CredentialManager.build_string(db_settings.connection_string, db_settings.credentials))
def add_logging(self):
self.add_singleton(LoggerABC, Logger)
def add_singleton(self, service_type: Union[type, object], service: Union[type, object] = None):
impl = None
if service is not None:
if isinstance(service, type):
impl = self.build_service_provider().build_service(service)
self._add_descriptor(impl, ServiceLifetimeEnum.singleton)
else:
if isinstance(service_type, type):
impl = self.build_service_provider().build_service(service_type)
self._add_descriptor(impl, ServiceLifetimeEnum.singleton)
def add_scoped(self, service_type: Type, service: Callable = None):
raise Exception('Not implemented')
def add_transient(self, service_type: Union[type], service: Union[type] = None):
if service is not None:
self._add_descriptor(service, ServiceLifetimeEnum.transient)
else:
self._add_descriptor(service_type, ServiceLifetimeEnum.transient)
def build_service_provider(self) -> ServiceProviderABC:
return ServiceProvider(self._service_descriptors, self._configuration, self._database_context)

View File

@@ -0,0 +1,71 @@
from abc import abstractmethod, ABC
from collections import Callable
from typing import Type
from cpl.database.database_settings import DatabaseSettings
from cpl.database.context.database_context_abc import DatabaseContextABC
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
class ServiceCollectionABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC for service providing
"""
pass
@abstractmethod
def add_db_context(self, db_context: Type[DatabaseContextABC], db_settings: DatabaseSettings):
"""
Adds database context
:param db_context:
:param db_settings:
:return:
"""
pass
@abstractmethod
def add_logging(self):
"""
Adds the CPL internal logger
"""
pass
@abstractmethod
def add_transient(self, service_type: Type, service: Callable = None):
"""
Adds a service with transient lifetime
:param service_type:
:param service:
:return:
"""
pass
@abstractmethod
def add_scoped(self, service_type: Type, service: Callable = None):
"""
Adds a service with scoped lifetime
:param service_type:
:param service:
:return:
"""
pass
@abstractmethod
def add_singleton(self, service_type: Type, service: Callable = None):
"""
Adds a service with singleton lifetime
:param service_type:
:param service:
:return:
"""
pass
@abstractmethod
def build_service_provider(self) -> ServiceProviderABC:
"""
Creates instance of the service provider
"""
pass

View File

@@ -0,0 +1,33 @@
from typing import Union, Optional
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
class ServiceDescriptor:
def __init__(self, implementation: Union[type, Optional[object]], lifetime: ServiceLifetimeEnum):
self._service_type = implementation
self._implementation = implementation
self._lifetime = lifetime
if not isinstance(implementation, type):
self._service_type = type(implementation)
else:
self._implementation = None
@property
def service_type(self) -> type:
return self._service_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

View File

@@ -0,0 +1,8 @@
from enum import Enum
class ServiceLifetimeEnum(Enum):
singleton = 0
scoped = 1 # not supported yet
transient = 2

View File

@@ -0,0 +1,90 @@
from collections import Callable
from inspect import signature, Parameter
from typing import Optional
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.database.context.database_context_abc import DatabaseContextABC
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.dependency_injection.service_descriptor import ServiceDescriptor
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
class ServiceProvider(ServiceProviderABC):
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
def _find_service(self, service_type: type) -> [ServiceDescriptor]:
for descriptor in self._service_descriptors:
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
return descriptor
return None
def _get_service(self, parameter: Parameter) -> 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)
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptor.implementation = implementation
return implementation
def build_service(self, service_type: type) -> object:
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 = []
for param in sig.parameters.items():
parameter = param[1]
if parameter.name != 'self' and parameter.annotation != Parameter.empty:
if 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))
return service_type(*params)
def get_service(self, service_type: type) -> Optional[Callable[object]]:
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)
if result.lifetime == ServiceLifetimeEnum.singleton:
result.implementation = implementation
return implementation

View File

@@ -0,0 +1,31 @@
from abc import abstractmethod, ABC
from collections import Callable
from typing import Type, Optional
class ServiceProviderABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC for service providing
"""
pass
@abstractmethod
def build_service(self, service_type: type) -> object:
"""
Creates instance of given type
:param service_type:
:return:
"""
pass
@abstractmethod
def get_service(self, instance_type: Type) -> Optional[Callable[object]]:
"""
Returns instance of given type
:param instance_type:
:return:
"""
pass

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.environment'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .application_environment_abc import ApplicationEnvironmentABC
from .environment_name_enum import EnvironmentNameEnum
from .application_environment import ApplicationEnvironment
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,98 @@
import pathlib
from datetime import datetime
from socket import gethostname
from typing import Optional
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.environment.environment_name_enum import EnvironmentNameEnum
class ApplicationEnvironment(ApplicationEnvironmentABC):
def __init__(self, name: EnvironmentNameEnum = EnvironmentNameEnum.production, crp: str = './'):
"""
Represents environment of the application
:param name:
:param crp:
"""
ApplicationEnvironmentABC.__init__(self)
self._environment_name: Optional[EnvironmentNameEnum] = name
self._app_name: Optional[str] = None
self._customer: Optional[str] = None
self._content_root_path: Optional[str] = crp
self._start_time: datetime = datetime.now()
self._end_time: datetime = datetime.now()
self._working_directory = pathlib.Path().absolute()
self._runtime_directory = pathlib.Path(__file__).parent.absolute()
@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 content_root_path(self) -> str:
return self._content_root_path
@content_root_path.setter
def content_root_path(self, content_root_path: str):
self._content_root_path = content_root_path
@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 self._working_directory
def set_working_directory(self, path: str = ''):
if path != '':
self._working_directory = path
return
self._working_directory = pathlib.Path().absolute()
@property
def runtime_directory(self) -> str:
return self._runtime_directory
def set_runtime_directory(self, file: str):
self._runtime_directory = pathlib.Path(file).parent.absolute()

View File

@@ -0,0 +1,85 @@
from abc import ABC, abstractmethod
from datetime import datetime
class ApplicationEnvironmentABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC of application environment
"""
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 content_root_path(self) -> str: pass
@content_root_path.setter
@abstractmethod
def content_root_path(self, content_root_path: 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):
"""
Sets the current runtime directory
:param runtime_directory:
:return:
"""
pass

View File

@@ -1,7 +1,7 @@
from enum import Enum
class EnvironmentName(Enum):
class EnvironmentNameEnum(Enum):
production = 'production'
staging = 'staging'

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.logging'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .logger_service import Logger
from .logger_abc import LoggerABC
from .logging_level_enum import LoggingLevelEnum
from .logging_settings import LoggingSettings
from .logging_settings_name_enum import LoggingSettingsNameEnum
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,82 @@
from abc import abstractmethod, ABC
class LoggerABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC for logging
"""
ABC.__init__(self)
@abstractmethod
def header(self, string: str):
"""
Writes a header message
:param string:
:return:
"""
pass
@abstractmethod
def trace(self, name: str, message: str):
"""
Writes a trace message
:param name:
:param message:
:return:
"""
pass
@abstractmethod
def debug(self, name: str, message: str):
"""
Writes a debug message
:param name:
:param message:
:return:
"""
pass
@abstractmethod
def info(self, name: str, message: str):
"""
Writes an information
:param name:
:param message:
:return:
"""
pass
@abstractmethod
def warn(self, name: str, message: str):
"""
Writes an warning
:param name:
:param message:
:return:
"""
pass
@abstractmethod
def error(self, name: str, message: str, ex: Exception = None):
"""
Writes an error
:param name:
:param message:
:param ex:
:return:
"""
pass
@abstractmethod
def fatal(self, name: str, message: str, ex: Exception = None):
"""
Writes an error and exits
:param name:
:param message:
:param ex:
:return:
"""
pass

View File

@@ -0,0 +1,233 @@
import datetime
import os
import traceback
from string import Template
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.logging.logger_abc import LoggerABC
from cpl.logging.logging_level_enum import LoggingLevelEnum
from cpl.logging.logging_settings import LoggingSettings
from cpl.time.time_format_settings import TimeFormatSettings
class Logger(LoggerABC):
def __init__(self, logging_settings: LoggingSettings, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC):
"""
Service for logging
:param logging_settings:
:param time_format:
:param app_runtime:
"""
LoggerABC.__init__(self)
self._env = env
self._log_settings: LoggingSettings = logging_settings
self._time_format_settings: TimeFormatSettings = time_format
self._log = Template(self._log_settings.filename).substitute(
date_time_now=self._env.date_time_now.strftime(self._time_format_settings.date_time_format),
start_time=self._env.start_time.strftime(self._time_format_settings.date_time_log_format)
)
self._path = self._log_settings.path
self._level = self._log_settings.level
self._console = self._log_settings.console
self.create()
def _get_datetime_now(self) -> str:
"""
Returns the date and time by given format
:return:
"""
try:
return datetime.datetime.now().strftime(self._time_format_settings.date_time_format)
except Exception as e:
self.error(__name__, 'Cannot get time', ex=e)
def _get_date(self) -> str:
"""
Returns the date by given format
:return:
"""
try:
return datetime.datetime.now().strftime(self._time_format_settings.date_format)
except Exception as e:
self.error(__name__, 'Cannot get date', ex=e)
def create(self) -> None:
"""
Creates path tree and logfile
:return:
"""
""" path """
try:
# check if log file path exists
if not os.path.exists(self._path):
os.makedirs(self._path)
except Exception as e:
self._fatal_console(__name__, 'Cannot create log dir', ex=e)
""" create new log file """
try:
# open log file, create if not exists
path = f'{self._path}{self._log}'
f = open(path, "w+")
Console.write_line(f'[{__name__}]: Using log file: {path}')
f.close()
except Exception as e:
self._fatal_console(__name__, 'Cannot open log file', ex=e)
def _append_log(self, string):
"""
Writes to logfile
:param string:
:return:
"""
try:
# open log file and append always
if not os.path.isdir(self._path):
self._fatal_console(__name__, 'Log directory not found')
with open(self._path + self._log, "a+", encoding="utf-8") as f:
f.write(string + '\n')
f.close()
except Exception as e:
self._fatal_console(__name__, f'Cannot append log file, message: {string}', ex=e)
def _get_string(self, name: str, level: LoggingLevelEnum, message: str) -> str:
"""
Returns input as log entry format
:param name:
:param level:
:param message:
:return:
"""
log_level = level.name
return f'<{self._get_datetime_now()}> [ {log_level} ] [ {name} ]: {message}'
def header(self, string: str):
# append log and print message
self._append_log(string)
Console.set_foreground_color(ForegroundColorEnum.default)
Console.write_line(string)
Console.set_foreground_color(ForegroundColorEnum.default)
def trace(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.TRACE, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.TRACE.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.TRACE.value:
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def debug(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.DEBUG, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.DEBUG.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.DEBUG.value:
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def info(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.INFO, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.INFO.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.INFO.value:
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def warn(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.WARN, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.WARN.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.WARN.value:
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def error(self, name: str, message: str, ex: Exception = None):
output = ''
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.ERROR, f'{ex} -> {tb}')
else:
output = self._get_string(name, LoggingLevelEnum.ERROR, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.ERROR.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.ERROR.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def fatal(self, name: str, message: str, ex: Exception = None):
output = ''
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.FATAL, f'{ex} -> {tb}')
else:
output = self._get_string(name, LoggingLevelEnum.FATAL, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.FATAL.value:
self._append_log(output)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.FATAL.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
exit()
def _fatal_console(self, name: str, message: str, ex: Exception = None):
"""
Writes an error to console only
:param name:
:param message:
:param ex:
:return:
"""
output = ''
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.ERROR, f'{ex} -> {tb}')
else:
output = self._get_string(name, LoggingLevelEnum.ERROR, message)
# check if message can be shown in console_old
if self._console.value >= LoggingLevelEnum.FATAL.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
exit()

View File

@@ -1,7 +1,7 @@
from enum import Enum
class LoggingLevel(Enum):
class LoggingLevelEnum(Enum):
OFF = 0 # Nothing
FATAL = 1 # Error that cause exit

View File

@@ -0,0 +1,62 @@
import traceback
from typing import Optional
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.logging.logging_level_enum import LoggingLevelEnum
from cpl.logging.logging_settings_name_enum import LoggingSettingsNameEnum
class LoggingSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._path: Optional[str] = None
self._filename: Optional[str] = None
self._console: Optional[LoggingLevelEnum] = None
self._level: Optional[LoggingLevelEnum] = None
@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) -> LoggingLevelEnum:
return self._console
@console.setter
def console(self, console: LoggingLevelEnum) -> None:
self._console = console
@property
def level(self) -> LoggingLevelEnum:
return self._level
@level.setter
def level(self, level: LoggingLevelEnum) -> None:
self._level = level
def from_dict(self, settings: dict):
try:
self._path = settings[LoggingSettingsNameEnum.path.value]
self._filename = settings[LoggingSettingsNameEnum.filename.value]
self._console = LoggingLevelEnum[settings[LoggingSettingsNameEnum.console_level.value]]
self._level = LoggingLevelEnum[settings[LoggingSettingsNameEnum.file_level.value]]
except Exception as e:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.write_line(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
Console.set_foreground_color(ForegroundColorEnum.default)

View File

@@ -1,7 +1,7 @@
from enum import Enum
class LoggingSettingsName(Enum):
class LoggingSettingsNameEnum(Enum):
path = 'Path'
filename = 'Filename'

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.mailing'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .email import EMail
from .email_client_service import EMailClient
from .email_client_abc import EMailClientABC
from .email_client_settings import EMailClientSettings
from .email_client_settings_name_enum import EMailClientSettingsNameEnum
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -4,6 +4,14 @@ import re
class 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:
"""
self._header: list[str] = header
self._subject: str = subject
@@ -65,15 +73,30 @@ class EMail:
@staticmethod
def check_mail(address: str) -> bool:
"""
Checks if an email is valid
:param address:
:return:
"""
return bool(re.search('^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(.\\w{2,3})+$', address))
def add_header(self, header: str):
"""
Adds header
:param header:
:return:
"""
if self._header is None:
self._header = []
self._header.append(header)
def add_receiver(self, receiver: str):
"""
Adds receiver
:param receiver:
:return:
"""
if self._receiver is None:
self._receiver = []
@@ -83,4 +106,9 @@ class EMail:
raise Exception(f'Invalid email: {receiver}')
def get_content(self, transceiver: str):
"""
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')

View File

@@ -0,0 +1,30 @@
from abc import abstractmethod, ABC
from cpl.mailing.email import EMail
class EMailClientABC(ABC):
@abstractmethod
def __init__(self):
"""
ABC to send emails
"""
ABC.__init__(self)
@abstractmethod
def connect(self):
"""
Connects to server
:return:
"""
pass
@abstractmethod
def send_mail(self, email: EMail):
"""
Sends email
:param email:
:return:
"""
pass

View File

@@ -2,19 +2,24 @@ import ssl
from smtplib import SMTP
from typing import Optional
from sh_edraft.environment.base.environment_base import EnvironmentBase
from sh_edraft.logging.base.logger_base import LoggerBase
from sh_edraft.mailing.base.email_client_base import EMailClientBase
from sh_edraft.mailing.model.email import EMail
from sh_edraft.mailing.model.email_client_settings import EMailClientSettings
from sh_edraft.service.base.service_base import ServiceBase
from sh_edraft.utils.credential_manager import CredentialManager
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.logging.logger_abc import LoggerABC
from cpl.mailing.email import EMail
from cpl.mailing.email_client_abc import EMailClientABC
from cpl.mailing.email_client_settings import EMailClientSettings
from cpl.utils.credential_manager import CredentialManager
class EMailClient(EMailClientBase):
class EMailClient(EMailClientABC):
def __init__(self, environment: EnvironmentBase, logger: LoggerBase, mail_settings: EMailClientSettings):
ServiceBase.__init__(self)
def __init__(self, environment: ApplicationEnvironmentABC, logger: LoggerABC, mail_settings: EMailClientSettings):
"""
Service to send emails
:param environment:
:param logger:
:param mail_settings:
"""
EMailClientABC.__init__(self)
self._environment = environment
self._mail_settings = mail_settings
@@ -25,6 +30,10 @@ class EMailClient(EMailClientBase):
self.create()
def create(self):
"""
Creates connection
:return:
"""
self._logger.trace(__name__, f'Started {__name__}.create')
self.connect()
self._logger.trace(__name__, f'Stopped {__name__}.create')
@@ -45,6 +54,10 @@ class EMailClient(EMailClientBase):
self._logger.trace(__name__, f'Stopped {__name__}.connect')
def login(self):
"""
Login to server
:return:
"""
self._logger.trace(__name__, 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}')

View File

@@ -1,14 +1,14 @@
import traceback
from sh_edraft.configuration.base.configuration_model_base import ConfigurationModelBase
from sh_edraft.console.console import Console
from sh_edraft.mailing.model.email_client_settings_name import EMailClientSettingsName
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.console.console import Console
from cpl.mailing.email_client_settings_name_enum import EMailClientSettingsNameEnum
class EMailClientSettings(ConfigurationModelBase):
class EMailClientSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelBase.__init__(self)
ConfigurationModelABC.__init__(self)
self._host: str = ''
self._port: int = 0
@@ -49,10 +49,10 @@ class EMailClientSettings(ConfigurationModelBase):
def from_dict(self, settings: dict):
try:
self._host = settings[EMailClientSettingsName.host.value]
self._port = settings[EMailClientSettingsName.port.value]
self._user_name = settings[EMailClientSettingsName.user_name.value]
self._credentials = settings[EMailClientSettingsName.credentials.value]
self._host = settings[EMailClientSettingsNameEnum.host.value]
self._port = settings[EMailClientSettingsNameEnum.port.value]
self._user_name = settings[EMailClientSettingsNameEnum.user_name.value]
self._credentials = settings[EMailClientSettingsNameEnum.credentials.value]
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -1,7 +1,7 @@
from enum import Enum
class EMailClientSettingsName(Enum):
class EMailClientSettingsNameEnum(Enum):
host = 'Host'
port = 'Port'

27
src/cpl/time/__init__.py Normal file
View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.time'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .time_format_settings import TimeFormatSettings
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -1,16 +1,16 @@
import traceback
from typing import Optional
from sh_edraft.configuration.base.configuration_model_base import ConfigurationModelBase
from sh_edraft.time.model.time_format_settings_names import TimeFormatSettingsNames
from sh_edraft.console.console import Console
from sh_edraft.console.model.foreground_color import ForegroundColor
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.time.time_format_settings_names_enum import TimeFormatSettingsNamesEnum
class TimeFormatSettings(ConfigurationModelBase):
class TimeFormatSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelBase.__init__(self)
ConfigurationModelABC.__init__(self)
self._date_format: Optional[str] = None
self._time_format: Optional[str] = None
self._date_time_format: Optional[str] = None
@@ -50,12 +50,12 @@ class TimeFormatSettings(ConfigurationModelBase):
def from_dict(self, settings: dict):
try:
self._date_format = settings[TimeFormatSettingsNames.date_format.value]
self._time_format = settings[TimeFormatSettingsNames.time_format.value]
self._date_time_format = settings[TimeFormatSettingsNames.date_time_format.value]
self._date_time_log_format = settings[TimeFormatSettingsNames.date_time_log_format.value]
self._date_format = settings[TimeFormatSettingsNamesEnum.date_format.value]
self._time_format = settings[TimeFormatSettingsNamesEnum.time_format.value]
self._date_time_format = settings[TimeFormatSettingsNamesEnum.date_time_format.value]
self._date_time_log_format = settings[TimeFormatSettingsNamesEnum.date_time_log_format.value]
except Exception as e:
Console.set_foreground_color(ForegroundColor.red)
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.write_line(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
Console.set_foreground_color(ForegroundColor.default)
Console.set_foreground_color(ForegroundColorEnum.default)

View File

@@ -1,7 +1,7 @@
from enum import Enum
class TimeFormatSettingsNames(Enum):
class TimeFormatSettingsNamesEnum(Enum):
date_format = 'DateFormat'
time_format = 'TimeFormat'

28
src/cpl/utils/__init__.py Normal file
View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl.utils'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .credential_manager import CredentialManager
from .string import String
from .pip import Pip
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -2,16 +2,35 @@ import base64
class CredentialManager:
"""
Handles credentials
"""
@staticmethod
def encrypt(string: str) -> str:
"""
Encode with base64
:param string:
:return:
"""
return base64.b64encode(string.encode('utf-8')).decode('utf-8')
@staticmethod
def decrypt(string: str) -> str:
"""
Decode with base64
:param string:
:return:
"""
return base64.b64decode(string).decode('utf-8')
@staticmethod
def build_string(string: str, credentials: str):
"""
Builds string with credentials in it
:param string:
:param credentials:
:return:
"""
return string.replace('$credentials', CredentialManager.decrypt(credentials))

113
src/cpl/utils/pip.py Normal file
View File

@@ -0,0 +1,113 @@
import subprocess
import sys
from contextlib import suppress
from typing import Optional
class Pip:
"""
Executes pip commands
"""
_executable = sys.executable
"""
Getter
"""
@classmethod
def get_executable(cls) -> str:
return cls._executable
"""
Setter
"""
@classmethod
def set_executable(cls, executable: str):
"""
Sets the executable
:param executable:
:return:
"""
if executable is not None:
cls._executable = executable
@classmethod
def reset_executable(cls):
"""
Resets the executable to system standard
:return:
"""
cls._executable = sys.executable
"""
Public utils functions
"""
@classmethod
def get_package(cls, package: str) -> Optional[str]:
"""
Gets given package py local pip list
:param package:
:return:
"""
result = None
with suppress(Exception):
result = subprocess.check_output([cls._executable, "-m", "pip", "show", package], stderr=subprocess.DEVNULL)
if result is None:
return None
new_package: list[str] = str(result, 'utf-8').lower().split('\n')
new_version = ''
for atr in new_package:
if 'version' in atr:
new_version = atr.split(': ')[1]
if new_version != '':
return f'{package}=={new_version}'
return package
@classmethod
def get_outdated(cls) -> bytes:
"""
Gets table of outdated packages
:return:
"""
return subprocess.check_output([cls._executable, "-m", "pip", "list", "--outdated"])
@classmethod
def install(cls, package: str, *args, source: str = None, stdout=None, stderr=None):
"""
Installs given package
:param package:
:param args:
:param source:
:param stdout:
:param stderr:
:return:
"""
pip_args = [cls._executable, "-m", "pip", "install"]
for arg in args:
pip_args.append(arg)
if source is not None:
pip_args.append(f'--extra-index-url')
pip_args.append(source)
pip_args.append(package)
subprocess.run(pip_args, stdout=stdout, stderr=stderr)
@classmethod
def uninstall(cls, package: str, stdout=None, stderr=None):
"""
Uninstalls given package
:param package:
:param stdout:
:param stderr:
:return:
"""
subprocess.run([cls._executable, "-m", "pip", "uninstall", "--yes", package], stdout=stdout, stderr=stderr)

37
src/cpl/utils/string.py Normal file
View File

@@ -0,0 +1,37 @@
import re
class String:
"""
Useful functions for strings
"""
@staticmethod
def convert_to_snake_case(name: str) -> str:
"""
Converts string to snake case
:param name:
:return:
"""
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', name)
return re.sub(pattern2, r'\1_\2', file_name).lower()
@staticmethod
def first_to_upper(string: str) -> str:
"""
Converts first char to upper
:param string:
:return:
"""
return f'{string[0].upper()}{string[1:]}'
@staticmethod
def first_to_lower(string: str) -> str:
"""
Converts first char to lower
:param string:
:return:
"""
return f'{string[0].lower()}{string[1:]}'

32
src/cpl_cli/__init__.py Normal file
View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'sh_cpl.cpl_cli'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .cli import CLI
from .command_abc import CommandABC
from .command_handler_service import CommandHandler
from .command_model import CommandModel
from .error import Error
from .main import main
from .startup import Startup
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,5 @@
{
"CLI": {
"PipPath": "https://pip.sh-edraft.de"
}
}

72
src/cpl_cli/cli.py Normal file
View File

@@ -0,0 +1,72 @@
from typing import Optional
from cpl.application.application_abc import ApplicationABC
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.console.console import Console
from cpl.dependency_injection import ServiceProviderABC
from cpl_cli.command.build_service import BuildService
from cpl_cli.command.generate_service import GenerateService
from cpl_cli.command.install_service import InstallService
from cpl_cli.command.new_service import NewService
from cpl_cli.command.publish_service import PublishService
from cpl_cli.command.start_service import StartService
from cpl_cli.command.uninstall_service import UninstallService
from cpl_cli.command.update_service import UpdateService
from cpl_cli.command_handler_service import CommandHandler
from cpl_cli.command_model import CommandModel
from cpl_cli.error import Error
from cpl_cli.command.help_service import HelpService
from cpl_cli.command.version_service import VersionService
class CLI(ApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
"""
CPL CLI
"""
ApplicationABC.__init__(self, config, services)
self._command_handler: Optional[CommandHandler] = None
def configure(self):
self._command_handler: CommandHandler = self._services.get_service(CommandHandler)
self._command_handler.add_command(CommandModel('build', ['h', 'B'], BuildService, True))
self._command_handler.add_command(CommandModel('generate', ['g', 'G'], GenerateService, True))
self._command_handler.add_command(CommandModel('help', ['h', 'H'], HelpService, False))
self._command_handler.add_command(CommandModel('install', ['i', 'I'], InstallService, True))
self._command_handler.add_command(CommandModel('new', ['n', 'N'], NewService, False))
self._command_handler.add_command(CommandModel('publish', ['p', 'P'], PublishService, True))
self._command_handler.add_command(CommandModel('start', ['s', 'S'], StartService, True))
self._command_handler.add_command(CommandModel('uninstall', ['ui', 'UI'], UninstallService, True))
self._command_handler.add_command(CommandModel('update', ['u', 'U'], UpdateService, True))
self._command_handler.add_command(CommandModel('version', ['v', 'V'], VersionService, False))
def main(self):
"""
Entry point of the CPL CLI
:return:
"""
try:
command = None
args = []
if len(self._configuration.additional_arguments) > 0:
command = self._configuration.additional_arguments[0]
if len(self._configuration.additional_arguments) > 1:
args = self._configuration.additional_arguments[1:]
else:
for cmd in self._command_handler.commands:
result = self._configuration.get_configuration(cmd.name)
if result is not None:
command = cmd.name
args.append(result)
if command is None:
Error.error(f'Expected command')
return
self._command_handler.handle(command, args)
except KeyboardInterrupt:
Console.write_line()
exit()

View File

@@ -0,0 +1,25 @@
import traceback
from typing import Optional
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.console.console import Console
from cpl_cli.cli_settings_name_enum import CLISettingsNameEnum
class CLISettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._pip_path: Optional[str] = None
@property
def pip_path(self) -> str:
return self._pip_path
def from_dict(self, settings: dict):
try:
self._pip_path = settings[CLISettingsNameEnum.pip_path.value]
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,6 @@
from enum import Enum
class CLISettingsNameEnum(Enum):
pip_path = 'PipPath'

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""
sh_cpl sh-edraft Common Python library
~~~~~~~~~~~~~~~~~~~
sh-edraft Common Python library
:copyright: (c) 2020 - 2021 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'cpl_cli.command'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
__version__ = '2021.4.0'
from collections import namedtuple
# imports:
from .build_service import BuildService
from .generate_service import GenerateService
from .help_service import HelpService
from .new_service import NewService
from .publish_service import PublishService
from .version_service import VersionService
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='2021', minor='04', micro='0')

View File

@@ -0,0 +1,22 @@
from cpl_cli.command_abc import CommandABC
from cpl_cli.publish.publisher_abc import PublisherABC
class BuildService(CommandABC):
def __init__(self, publisher: PublisherABC):
"""
Service for the CLI command build
:param publisher:
"""
CommandABC.__init__(self)
self._publisher = publisher
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
self._publisher.build()

View File

@@ -0,0 +1,162 @@
import os
from collections import Callable
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.console.console import Console
from cpl.utils.string import String
from cpl_cli.command_abc import CommandABC
from cpl_cli.templates.generate.init_template import InitTemplate
from cpl_cli.templates.generate.abc_template import ABCTemplate
from cpl_cli.templates.generate.class_template import ClassTemplate
from cpl_cli.templates.generate.configmodel_template import ConfigModelTemplate
from cpl_cli.templates.generate.enum_template import EnumTemplate
from cpl_cli.templates.generate.service_template import ServiceTemplate
from cpl_cli.templates.generate.thread_template import ThreadTemplate
from cpl_cli.templates.template_file_abc import TemplateFileABC
class GenerateService(CommandABC):
def __init__(self, configuration: ConfigurationABC):
"""
Service for the CLI command generate
:param configuration:
"""
CommandABC.__init__(self)
self._schematics = {
"abc": {
"Upper": "ABC",
"Template": ABCTemplate
},
"class": {
"Upper": "",
"Template": ClassTemplate
},
"enum": {
"Upper": "Enum",
"Template": EnumTemplate
},
"service": {
"Upper": "Service",
"Template": ServiceTemplate
},
"settings": {
"Upper": "Settings",
"Template": ConfigModelTemplate
},
"thread": {
"Upper": "Thread",
"Template": ThreadTemplate
}
}
self._config = configuration
self._env = self._config.environment
@staticmethod
def _help(message: str):
"""
Internal help output
:param message:
:return:
"""
Console.error(message)
schematics = [
'abc (a|A)',
'class (c|C)',
'enum (e|E)',
'service (s|S)',
'settings (st|ST)'
]
Console.write_line('Available Schematics:')
for name in schematics:
Console.write(f'\n\t{name} ')
@staticmethod
def _create_file(file_path: str, value: str):
"""
Creates the given file with content
:param file_path:
:param value:
:return:
"""
with open(file_path, 'w') as template:
template.write(value)
template.close()
def _generate(self, schematic: str, name: str, template: Callable[TemplateFileABC]):
"""
Generates files by given schematic, name and template
:param schematic:
:param name:
:param template:
:return:
"""
class_name = name
rel_path = ''
if '/' in name:
parts = name.split('/')
rel_path = '/'.join(parts[:-1])
class_name = parts[len(parts) - 1]
template = template(class_name, schematic, self._schematics[schematic]["Upper"], rel_path)
file_path = os.path.join(self._env.working_directory, template.path, template.name)
if not os.path.isdir(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
directory = ''
for subdir in template.path.split('/'):
directory = os.path.join(directory, subdir)
if subdir != 'src':
file = InitTemplate(class_name, schematic, self._schematics[schematic]["Upper"], rel_path)
Console.spinner(
f'Creating {os.path.abspath(directory)}/{file.name}',
self._create_file,
os.path.join(os.path.abspath(directory), file.name),
file.value,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
if os.path.isfile(file_path):
Console.error(f'{String.first_to_upper(schematic)} already exists!')
exit()
message = f'Creating {self._env.working_directory}/{template.path}/{template.name}'
if template.path == '':
message = f'Creating {self._env.working_directory}/{template.name}'
Console.spinner(
message,
self._create_file,
file_path,
template.value,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
if len(args) == 0:
self._help('Usage: cpl generate <schematic> [options]')
exit()
schematic = args[0]
name = self._config.get_configuration(schematic)
if name is None:
name = Console.read(f'Name for the {args[0]}: ')
if schematic in self._schematics:
s = self._schematics[schematic]
self._generate(schematic, name, s["Template"])
else:
self._help('Usage: cpl generate <schematic> [options]')
exit()

View File

@@ -0,0 +1,37 @@
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl_cli.command_abc import CommandABC
class HelpService(CommandABC):
def __init__(self):
"""
Service for CLI command help
"""
CommandABC.__init__(self)
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
Console.write_line('Available Commands:')
commands = [
['build (b|B)', 'Prepares files for publish into an output directory named dist/ at the given output path. Must be executed from within a workspace directory.'],
['generate (g|G)', 'Generate a new file.'],
['help (h|H)', 'Lists available command and their short descriptions.'],
['install (i|I)', 'With argument installs packages to project, without argument installs project dependencies.'],
['new (n|N)', 'Creates new CPL project.'],
['publish (p|P)', 'Prepares files for publish into an output directory named dist/ at the given output path and executes setup.py. Must be executed from within a library workspace directory.'],
['start (s|S)', 'Starts CPL project, restarting on file changes'],
['uninstall (ui|UI)', 'Uninstalls packages from project.'],
['update (u|u)', 'Update CPL and project dependencies.'],
['version (v|V)', 'Outputs CPL CLI version.']
]
for name, description in commands:
Console.set_foreground_color(ForegroundColorEnum.blue)
Console.write(f'\n\t{name} ')
Console.set_foreground_color(ForegroundColorEnum.default)
Console.write(f'{description}')

View File

@@ -0,0 +1,165 @@
import json
import os
import subprocess
from packaging import version
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.utils.pip import Pip
from cpl_cli.cli_settings import CLISettings
from cpl_cli.command_abc import CommandABC
from cpl_cli.configuration.build_settings import BuildSettings
from cpl_cli.configuration.project_settings import ProjectSettings
from cpl_cli.configuration.settings_helper import SettingsHelper
from cpl_cli.error import Error
class InstallService(CommandABC):
def __init__(self, env: ApplicationEnvironmentABC, build_settings: BuildSettings, project_settings: ProjectSettings,
cli_settings: CLISettings):
"""
Service for the CLI command install
:param env:
:param build_settings:
:param project_settings:
:param cli_settings:
"""
CommandABC.__init__(self)
self._env = env
self._build_settings = build_settings
self._project_settings = project_settings
self._cli_settings = cli_settings
def _install_project(self):
"""
Installs dependencies of CPl project
:return:
"""
if self._project_settings is None or self._build_settings is None:
Error.error('The command requires to be run in an CPL project, but a project could not be found.')
return
if self._project_settings.dependencies is None:
Error.error('Found invalid dependencies in cpl.json.')
return
Pip.set_executable(self._project_settings.python_executable)
for dependency in self._project_settings.dependencies:
Console.spinner(
f'Installing: {dependency}',
Pip.install, dependency,
source=self._cli_settings.pip_path if 'sh_cpl' in dependency else None,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
Pip.reset_executable()
def _install_package(self, package: str):
"""
Installs given package
:param package:
:return:
"""
is_already_in_project = False
Pip.set_executable(self._project_settings.python_executable)
if self._project_settings is None or self._build_settings is None:
Error.error('The command requires to be run in an CPL project, but a project could not be found.')
return
if self._project_settings.dependencies is None:
Error.error('Found invalid dependencies in cpl.json.')
return
package_version = ''
name = package
if '==' in package:
name = package.split('==')[0]
package_version = package.split('==')[1]
to_remove_list = []
for dependency in self._project_settings.dependencies:
dependency_version = ''
if '==' in dependency:
dependency_version = dependency.split('==')[1]
if name in dependency:
if package_version != '' and version.parse(package_version) != version.parse(dependency_version):
to_remove_list.append(dependency)
break
else:
is_already_in_project = True
for to_remove in to_remove_list:
self._project_settings.dependencies.remove(to_remove)
local_package = Pip.get_package(package)
if local_package is not None and local_package in self._project_settings.dependencies:
Error.warn(f'Package {local_package} is already installed.')
return
elif is_already_in_project:
Error.warn(f'Package {package} is already installed.')
return
Console.spinner(
f'Installing: {package}',
Pip.install, package,
source=self._cli_settings.pip_path if 'sh_cpl' in package else None,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
new_package = Pip.get_package(name)
if new_package is None \
or '==' in package and \
version.parse(package.split('==')[1]) != version.parse(new_package.split('==')[1]):
Console.error(f'Installation of package {package} failed')
return
if not is_already_in_project:
new_name = package
if '==' in new_package:
new_name = new_package
elif '==' in name:
new_name = name
if '/' in new_name:
new_name = new_name.split('/')[0]
if '\r' in new_name:
new_name = new_name.replace('\r', '')
self._project_settings.dependencies.append(new_name)
config = {
ProjectSettings.__name__: SettingsHelper.get_project_settings_dict(self._project_settings),
BuildSettings.__name__: SettingsHelper.get_build_settings_dict(self._build_settings)
}
with open(os.path.join(self._env.working_directory, 'cpl.json'), 'w') as project_file:
project_file.write(json.dumps(config, indent=2))
project_file.close()
Pip.reset_executable()
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
if len(args) == 0:
self._install_project()
else:
self._install_package(args[0])

View File

@@ -0,0 +1,231 @@
import json
import os
import sys
from typing import Optional
from packaging import version
import cpl
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.console.console import Console
from cpl.utils.string import String
from cpl_cli.command_abc import CommandABC
from cpl_cli.configuration.build_settings import BuildSettings
from cpl_cli.configuration.build_settings_name_enum import BuildSettingsNameEnum
from cpl_cli.configuration.project_settings import ProjectSettings
from cpl_cli.configuration.project_settings_name_enum import ProjectSettingsNameEnum
from cpl_cli.configuration.project_type_enum import ProjectTypeEnum
from cpl_cli.configuration.version_settings_name_enum import VersionSettingsNameEnum
from cpl_cli.source_creator.console_builder import ConsoleBuilder
from cpl_cli.source_creator.library_builder import LibraryBuilder
class NewService(CommandABC):
def __init__(self, configuration: ConfigurationABC):
"""
Service for the CLI command new
:param configuration:
"""
CommandABC.__init__(self)
self._config = configuration
self._env = self._config.environment
self._project: ProjectSettings = ProjectSettings()
self._project_dict = {}
self._build: BuildSettings = BuildSettings()
self._build_dict = {}
self._project_json = {}
self._command: str = ''
self._use_application_api: bool = False
self._use_startup: bool = False
self._use_service_providing: bool = False
@staticmethod
def _help(message: str):
"""
Internal help output
:param message:
:return:
"""
Console.error(message)
schematics = [
'console (c|C) <name>',
]
Console.write_line('Available Schematics:')
for name in schematics:
Console.write(f'\n\t{name} ')
def _create_project_settings(self, name: str):
self._project_dict = {
ProjectSettingsNameEnum.name.value: name,
ProjectSettingsNameEnum.version.value: {
VersionSettingsNameEnum.major.value: '0',
VersionSettingsNameEnum.minor.value: '0',
VersionSettingsNameEnum.micro.value: '0'
},
ProjectSettingsNameEnum.author.value: '',
ProjectSettingsNameEnum.author_email.value: '',
ProjectSettingsNameEnum.description.value: '',
ProjectSettingsNameEnum.long_description.value: '',
ProjectSettingsNameEnum.url.value: '',
ProjectSettingsNameEnum.copyright_date.value: '',
ProjectSettingsNameEnum.copyright_name.value: '',
ProjectSettingsNameEnum.license_name.value: '',
ProjectSettingsNameEnum.license_description.value: '',
ProjectSettingsNameEnum.dependencies.value: [
f'sh_cpl=={version.parse(cpl.__version__)}'
],
ProjectSettingsNameEnum.python_version.value: f'>={sys.version.split(" ")[0]}',
ProjectSettingsNameEnum.python_path.value: {
sys.platform: ''
},
ProjectSettingsNameEnum.classifiers.value: []
}
self._project.from_dict(self._project_dict)
def _create_build_settings(self):
main = f'{String.convert_to_snake_case(self._project.name)}.main'
if self._command == ProjectTypeEnum.library.value:
main = f'{String.convert_to_snake_case(self._project.name)}_cli.main'
self._build_dict = {
BuildSettingsNameEnum.project_type.value: self._command,
BuildSettingsNameEnum.source_path.value: 'src',
BuildSettingsNameEnum.output_path.value: 'dist',
BuildSettingsNameEnum.main.value: main,
BuildSettingsNameEnum.entry_point.value: self._project.name,
BuildSettingsNameEnum.include_package_data.value: False,
BuildSettingsNameEnum.included.value: [],
BuildSettingsNameEnum.excluded.value: [
'*/__pycache__',
'*/logs',
'*/tests'
],
BuildSettingsNameEnum.package_data.value: {}
}
self._build.from_dict(self._build_dict)
def _create_project_json(self):
"""
Creates cpl.json content
:return:
"""
self._project_json = {
ProjectSettings.__name__: self._project_dict,
BuildSettings.__name__: self._build_dict
}
def _get_project_path(self) -> Optional[str]:
"""
Gets project path
:return:
"""
project_path = os.path.join(self._env.working_directory, self._project.name)
if os.path.isdir(project_path) and len(os.listdir(project_path)) > 0:
Console.error('Project path is not empty\n')
return None
return project_path
def _get_project_information(self):
"""
Gets project information's from user
:return:
"""
result = Console.read('Do you want to use application base? (y/n) ')
if result.lower() == 'y':
self._use_application_api = True
result = Console.read('Do you want to use startup? (y/n) ')
if result.lower() == 'y':
self._use_startup = True
else:
result = Console.read('Do you want to use service providing? (y/n) ')
if result.lower() == 'y':
self._use_service_providing = True
Console.set_foreground_color(ForegroundColorEnum.default)
def _console(self, args: list[str]):
"""
Generates new console project
:param args:
:return:
"""
name = self._config.get_configuration(self._command)
self._create_project_settings(name)
self._create_build_settings()
self._create_project_json()
path = self._get_project_path()
if path is None:
return
self._get_project_information()
try:
ConsoleBuilder.build(
path,
self._use_application_api,
self._use_startup,
self._use_service_providing,
self._project.name,
self._project_json
)
except Exception as e:
Console.error('Could not create project', str(e))
def _library(self, args: list[str]):
"""
Generates new library project
:param args:
:return:
"""
name = self._config.get_configuration(self._command)
self._create_project_settings(name)
self._create_build_settings()
self._create_project_json()
path = self._get_project_path()
if path is None:
return
self._get_project_information()
try:
LibraryBuilder.build(
path,
self._use_application_api,
self._use_startup,
self._use_service_providing,
self._project.name,
self._project_json
)
except Exception as e:
Console.error('Could not create project', str(e))
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
if len(args) == 0:
self._help('Usage: cpl new <schematic> [options]')
return
self._command = str(args[0]).lower()
if self._command == ProjectTypeEnum.console.value:
self._console(args)
elif self._command == ProjectTypeEnum.library.value:
self._library(args)
else:
self._help('Usage: cpl new <schematic> [options]')
return

View File

@@ -0,0 +1,23 @@
from cpl.console.console import Console
from cpl_cli.command_abc import CommandABC
from cpl_cli.publish.publisher_abc import PublisherABC
class PublishService(CommandABC):
def __init__(self, publisher: PublisherABC):
"""
Service for the CLI command publish
:param publisher:
"""
CommandABC.__init__(self)
self._publisher = publisher
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
self._publisher.publish()

View File

@@ -0,0 +1,22 @@
from cpl_cli.command_abc import CommandABC
from cpl_cli.live_server.live_server_service import LiveServerService
class StartService(CommandABC):
def __init__(self, live_server: LiveServerService):
"""
Service for the CLI command start
:param live_server:
"""
CommandABC.__init__(self)
self._live_server = live_server
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
self._live_server.start()

View File

@@ -0,0 +1,82 @@
import json
import os
import subprocess
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.utils.pip import Pip
from cpl_cli.command_abc import CommandABC
from cpl_cli.configuration.build_settings import BuildSettings
from cpl_cli.configuration.project_settings import ProjectSettings
from cpl_cli.configuration.settings_helper import SettingsHelper
class UninstallService(CommandABC):
def __init__(self, env: ApplicationEnvironmentABC, build_settings: BuildSettings,
project_settings: ProjectSettings):
"""
Service for the CLI command uninstall
:param env:
:param build_settings:
:param project_settings:
"""
CommandABC.__init__(self)
self._env = env
self._build_settings = build_settings
self._project_settings = project_settings
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
if len(args) == 0:
Console.error(f'Expected package')
Console.error(f'Usage: cpl uninstall <package>')
return
Pip.set_executable(self._project_settings.python_executable)
package = args[0]
is_in_dependencies = False
pip_package = Pip.get_package(package)
for dependency in self._project_settings.dependencies:
if package in dependency:
is_in_dependencies = True
package = dependency
if not is_in_dependencies and pip_package is None:
Console.error(f'Package {package} not found')
return
elif not is_in_dependencies and pip_package is not None:
package = pip_package
Console.spinner(
f'Uninstalling: {package}',
Pip.uninstall, package,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
if package in self._project_settings.dependencies:
self._project_settings.dependencies.remove(package)
config = {
ProjectSettings.__name__: SettingsHelper.get_project_settings_dict(self._project_settings),
BuildSettings.__name__: SettingsHelper.get_build_settings_dict(self._build_settings)
}
with open(os.path.join(self._env.working_directory, 'cpl.json'), 'w') as project_file:
project_file.write(json.dumps(config, indent=2))
project_file.close()
Console.write_line(f'Removed {package}')
Pip.reset_executable()

View File

@@ -0,0 +1,151 @@
import json
import os
import subprocess
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.utils.pip import Pip
from cpl_cli.cli_settings import CLISettings
from cpl_cli.command_abc import CommandABC
from cpl_cli.configuration import BuildSettings
from cpl_cli.configuration.project_settings import ProjectSettings
from cpl_cli.configuration.settings_helper import SettingsHelper
class UpdateService(CommandABC):
def __init__(self,
env: ApplicationEnvironmentABC,
build_settings: BuildSettings,
project_settings: ProjectSettings,
cli_settings: CLISettings):
"""
Service for the CLI command update
:param env:
:param build_settings:
:param project_settings:
:param cli_settings:
"""
CommandABC.__init__(self)
self._env = env
self._build_settings = build_settings
self._project_settings = project_settings
self._cli_settings = cli_settings
def _collect_project_dependencies(self) -> list[tuple]:
"""
Collects project dependencies
:return:
"""
dependencies = []
for package in self._project_settings.dependencies:
name = package
if '==' in package:
name = package.split('==')[0]
dependencies.append((package, name))
return dependencies
def _update_project_dependencies(self, dependencies):
"""
Updates project dependencies
:return:
"""
for package, name in dependencies:
Pip.install(
name,
'--upgrade',
'--upgrade-strategy',
'eager',
source=self._cli_settings.pip_path if 'sh_cpl' in name else None,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
new_package = Pip.get_package(name)
if new_package is None:
Console.error(f'Update for package {package} failed')
return
self._project_json_update_dependency(package, new_package)
def _check_project_dependencies(self):
"""
Checks project dependencies for updates
:return:
"""
dependencies = Console.spinner(
'Collecting installed dependencies', self._collect_project_dependencies,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
Console.spinner(
'Updating installed dependencies', self._update_project_dependencies, dependencies,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
Console.write_line(f'Found {len(self._project_settings.dependencies)} dependencies.')
@staticmethod
def _check_outdated():
"""
Checks for outdated packages in project
:return:
"""
table_str: bytes = Console.spinner(
'Analyzing for available package updates', Pip.get_outdated,
text_foreground_color=ForegroundColorEnum.green,
spinner_foreground_color=ForegroundColorEnum.cyan
)
table = str(table_str, 'utf-8').split('\n')
if len(table) > 1 and table[0] != '':
Console.write_line('\tAvailable updates for packages:')
for row in table:
Console.write_line(f'\t{row}')
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(f'\tUpdate with {Pip.get_executable()} -m pip install --upgrade <package>')
Console.set_foreground_color(ForegroundColorEnum.default)
def _project_json_update_dependency(self, old_package: str, new_package: str):
"""
Writes new package version to cpl.json
:param old_package:
:param new_package:
:return:
"""
if old_package in self._project_settings.dependencies:
index = self._project_settings.dependencies.index(old_package)
if '/' in new_package:
new_package = new_package.split('/')[0]
if '\r' in new_package:
new_package = new_package.replace('\r', '')
self._project_settings.dependencies[index] = new_package
config = {
ProjectSettings.__name__: SettingsHelper.get_project_settings_dict(self._project_settings),
BuildSettings.__name__: SettingsHelper.get_build_settings_dict(self._build_settings)
}
with open(os.path.join(self._env.working_directory, 'cpl.json'), 'w') as project:
project.write(json.dumps(config, indent=2))
project.close()
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
Pip.set_executable(self._project_settings.python_executable)
self._check_project_dependencies()
self._check_outdated()
Pip.reset_executable()

View File

@@ -0,0 +1,54 @@
import pkgutil
import sys
import platform
import pkg_resources
import cpl
import cpl_cli
from cpl.console.console import Console
from cpl.console.foreground_color_enum import ForegroundColorEnum
from cpl_cli.command_abc import CommandABC
class VersionService(CommandABC):
def __init__(self):
"""
Service for the CLI command version
"""
CommandABC.__init__(self)
def run(self, args: list[str]):
"""
Entry point of command
:param args:
:return:
"""
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.banner('CPL CLI')
Console.set_foreground_color(ForegroundColorEnum.default)
if '__version__' in dir(cpl_cli):
Console.write_line(f'Common Python library CLI: ')
Console.write(cpl_cli.__version__)
Console.write_line(f'Python: ')
Console.write(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')
Console.write_line(f'OS: {platform.system()} {platform.processor()}')
Console.write_line('\nCPL packages:')
packages = []
for importer, modname, is_pkg in pkgutil.iter_modules(cpl.__path__):
module = importer.find_module(modname).load_module(modname)
if '__version__' in dir(module):
packages.append([f'{modname}', module.__version__])
Console.table(['Name', 'Version'], packages)
Console.write_line('\nPython packages:')
packages = []
dependencies = dict(tuple(str(ws).split()) for ws in pkg_resources.working_set)
for p in dependencies:
packages.append([p, dependencies[p]])
Console.table(['Name', 'Version'], packages)

View File

@@ -0,0 +1,11 @@
from abc import abstractmethod, ABC
class CommandABC(ABC):
@abstractmethod
def __init__(self):
ABC.__init__(self)
@abstractmethod
def run(self, args: list[str]): pass

View File

@@ -0,0 +1,54 @@
import os
from abc import ABC
from cpl.configuration.configuration_abc import ConfigurationABC
from cpl.console.console import Console
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl_cli.error import Error
from cpl_cli.command_model import CommandModel
class CommandHandler(ABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
"""
Service to handle incoming commands and args
:param config:
:param services:
"""
ABC.__init__(self)
self._config = config
self._env = self._config.environment
self._services = services
self._commands: list[CommandModel] = []
@property
def commands(self) -> list[CommandModel]:
return self._commands
def add_command(self, cmd: CommandModel):
self._commands.append(cmd)
def remove_command(self, cmd: CommandModel):
self._commands.remove(cmd)
def handle(self, cmd: str, args: list[str]):
"""
Handles incoming commands and args
:param cmd:
:param args:
:return:
"""
for command in self._commands:
if cmd == command.name or cmd in command.aliases:
if command.is_project_needed and not os.path.isfile(os.path.join(self._env.working_directory, 'cpl.json')):
Error.error('The command requires to be run in an CPL project, but a project could not be found.')
return
if command.is_project_needed:
self._config.add_json_file('cpl.json', optional=True, output=False)
self._services.get_service(command.command).run(args)
Console.write('\n')

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