holado 0.4.1__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of holado might be problematic. Click here for more details.

Files changed (93) hide show
  1. holado/common/context/session_context.py +6 -0
  2. holado/common/handlers/object.py +6 -0
  3. {holado-0.4.1.dist-info → holado-0.5.2.dist-info}/METADATA +2 -1
  4. {holado-0.4.1.dist-info → holado-0.5.2.dist-info}/RECORD +93 -43
  5. holado_ais/ais/ais_messages.py +44 -15
  6. holado_ais/ais/patch_pyais.py +47 -176
  7. holado_ais/tests/behave/steps/ais/ais_messages_steps.py +2 -6
  8. holado_core/common/resource/persisted_data_manager.py +4 -5
  9. holado_core/common/resource/resource_manager.py +4 -18
  10. holado_core/common/tools/converters/converter.py +14 -3
  11. holado_core/common/tools/tools.py +9 -2
  12. holado_core/tools/abstracts/blocking_command_service.py +9 -1
  13. holado_data/data/generator/generator_manager.py +1 -13
  14. holado_db/tools/db/clients/base/db_audit.py +94 -0
  15. holado_db/tools/db/clients/base/db_client.py +145 -59
  16. holado_db/tools/db/clients/postgresql/postgresql_audit.py +75 -0
  17. holado_db/tools/db/clients/postgresql/postgresql_client.py +4 -0
  18. holado_db/tools/db/clients/sqlite/sqlite_audit.py +70 -0
  19. holado_db/tools/db/clients/sqlite/sqlite_client.py +4 -0
  20. holado_django/__init__.py +31 -0
  21. holado_django/server/HOWTO.txt +27 -0
  22. holado_django/server/django_projects/rest_api/db.sqlite3 +0 -0
  23. holado_django/server/django_projects/rest_api/manage.py +22 -0
  24. holado_django/server/django_projects/rest_api/rest_api/__init__.py +0 -0
  25. holado_django/server/django_projects/rest_api/rest_api/application/__init__.py +0 -0
  26. holado_django/server/django_projects/rest_api/rest_api/application/admin.py +3 -0
  27. holado_django/server/django_projects/rest_api/rest_api/application/apps.py +9 -0
  28. holado_django/server/django_projects/rest_api/rest_api/application/migrations/__init__.py +0 -0
  29. holado_django/server/django_projects/rest_api/rest_api/application/models.py +3 -0
  30. holado_django/server/django_projects/rest_api/rest_api/application/tests.py +3 -0
  31. holado_django/server/django_projects/rest_api/rest_api/application/views.py +6 -0
  32. holado_django/server/django_projects/rest_api/rest_api/asgi.py +16 -0
  33. holado_django/server/django_projects/rest_api/rest_api/settings.py +130 -0
  34. holado_django/server/django_projects/rest_api/rest_api/urls.py +35 -0
  35. holado_django/server/django_projects/rest_api/rest_api/wsgi.py +16 -0
  36. holado_django/server/django_server.py +110 -0
  37. holado_django/server/grpc_django_server.py +57 -0
  38. holado_django/server/patch_djangogrpcframework.py +46 -0
  39. holado_django/tests/behave/steps/__init__.py +16 -0
  40. holado_django/tests/behave/steps/django_server_steps.py +83 -0
  41. holado_grpc/tests/behave/steps/private/api/grpc_steps.py +9 -9
  42. holado_logging/common/logging/holado_logger.py +1 -6
  43. holado_multitask/multitasking/multitask_manager.py +36 -10
  44. holado_multitask/multithreading/thread.py +13 -7
  45. holado_multitask/multithreading/timer.py +3 -0
  46. holado_python/common/tools/datetime.py +31 -13
  47. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
  48. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
  49. holado_rabbitmq/tools/rabbitmq/rabbitmq_blocking_client.py +24 -2
  50. holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +2 -2
  51. holado_report/report/builders/failure_report_builder.py +129 -0
  52. holado_report/report/report_manager.py +4 -0
  53. holado_rest/api/rest/TODO.txt +1 -1
  54. holado_rest/api/rest/rest_client.py +19 -7
  55. holado_rest/tests/behave/steps/api/rest_client_steps.py +1 -2
  56. holado_rest/tests/behave/steps/private/api/rest_steps.py +11 -13
  57. holado_system/system/command/command.py +31 -9
  58. holado_system/system/global_system.py +3 -3
  59. test_holado/environment.py +6 -4
  60. test_holado/features/NonReg/api/REST.feature +7 -2
  61. test_holado/features/NonReg/api/gRPC.feature +0 -6
  62. test_holado/features/NonReg/holado_ais/message_types/type-10.feature +38 -0
  63. test_holado/features/NonReg/holado_ais/message_types/type-12.feature +37 -0
  64. test_holado/features/NonReg/holado_ais/message_types/type-14.feature +36 -0
  65. test_holado/features/NonReg/holado_ais/message_types/type-15.feature +36 -0
  66. test_holado/features/NonReg/holado_ais/message_types/type-16.feature +38 -0
  67. test_holado/features/NonReg/holado_ais/message_types/type-17.feature +46 -0
  68. test_holado/features/NonReg/holado_ais/message_types/type-18.feature +37 -0
  69. test_holado/features/NonReg/holado_ais/message_types/type-19.feature +38 -0
  70. test_holado/features/NonReg/holado_ais/message_types/type-1_2_3.feature +42 -0
  71. test_holado/features/NonReg/holado_ais/message_types/type-20.feature +38 -0
  72. test_holado/features/NonReg/holado_ais/message_types/type-21.feature +37 -0
  73. test_holado/features/NonReg/holado_ais/message_types/type-22.feature +84 -0
  74. test_holado/features/NonReg/holado_ais/message_types/type-23.feature +49 -0
  75. test_holado/features/NonReg/holado_ais/message_types/type-24.feature +72 -0
  76. test_holado/features/NonReg/holado_ais/message_types/type-25.feature +143 -0
  77. test_holado/features/NonReg/holado_ais/message_types/type-26.feature +144 -0
  78. test_holado/features/NonReg/holado_ais/message_types/type-27.feature +36 -0
  79. test_holado/features/NonReg/holado_ais/message_types/type-4_11.feature +39 -0
  80. test_holado/features/NonReg/holado_ais/message_types/type-5.feature +33 -0
  81. test_holado/features/NonReg/holado_ais/message_types/type-6.feature +37 -0
  82. test_holado/features/NonReg/holado_ais/message_types/type-7_13.feature +43 -0
  83. test_holado/features/NonReg/holado_ais/message_types/type-8.feature +37 -0
  84. test_holado/features/NonReg/holado_ais/message_types/type-9.feature +37 -0
  85. test_holado/initialize_holado.py +62 -0
  86. test_holado/logging.conf +3 -0
  87. test_holado/tools/django/api_grpc/manage.py +2 -0
  88. test_holado/tools/django/api_grpc/patch_djangogrpcframework.py +42 -0
  89. test_holado/tools/django/api_rest/db.sqlite3 +0 -0
  90. {holado-0.4.1.dist-info → holado-0.5.2.dist-info}/WHEEL +0 -0
  91. {holado-0.4.1.dist-info → holado-0.5.2.dist-info}/licenses/LICENSE +0 -0
  92. /holado_helper/holado_module_template/{test → tests}/behave/steps/__init__.py +0 -0
  93. /holado_helper/holado_module_template/{test → tests}/behave/steps/private/__init__.py +0 -0
@@ -0,0 +1,130 @@
1
+ """
2
+ Django settings for server_rest_example project.
3
+
4
+ Generated by 'django-admin startproject' using Django 5.2.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/5.2/topics/settings/
8
+
9
+ For the full list of settings and their values, see
10
+ https://docs.djangoproject.com/en/5.2/ref/settings/
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
16
+ BASE_DIR = Path(__file__).resolve().parent.parent
17
+
18
+
19
+ # Quick-start development settings - unsuitable for production
20
+ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
21
+
22
+ # SECURITY WARNING: keep the secret key used in production secret!
23
+ SECRET_KEY = 'django-insecure-(4nx+&@_ylqw(sa25syu5gdnu1q0bhyy69%*)^al_=2*%c*=ar'
24
+
25
+ # SECURITY WARNING: don't run with debug turned on in production!
26
+ DEBUG = True
27
+
28
+ ALLOWED_HOSTS = []
29
+
30
+
31
+ # Application definition
32
+
33
+ INSTALLED_APPS = [
34
+ 'django.contrib.admin',
35
+ 'django.contrib.auth',
36
+ 'django.contrib.contenttypes',
37
+ 'django.contrib.sessions',
38
+ 'django.contrib.messages',
39
+ 'django.contrib.staticfiles',
40
+ 'rest_framework',
41
+ ]
42
+
43
+ MIDDLEWARE = [
44
+ 'django.middleware.security.SecurityMiddleware',
45
+ 'django.contrib.sessions.middleware.SessionMiddleware',
46
+ 'django.middleware.common.CommonMiddleware',
47
+ 'django.middleware.csrf.CsrfViewMiddleware',
48
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
49
+ 'django.contrib.messages.middleware.MessageMiddleware',
50
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51
+ ]
52
+
53
+ ROOT_URLCONF = 'rest_api.urls'
54
+
55
+ TEMPLATES = [
56
+ {
57
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58
+ 'DIRS': [],
59
+ 'APP_DIRS': True,
60
+ 'OPTIONS': {
61
+ 'context_processors': [
62
+ 'django.template.context_processors.request',
63
+ 'django.contrib.auth.context_processors.auth',
64
+ 'django.contrib.messages.context_processors.messages',
65
+ ],
66
+ },
67
+ },
68
+ ]
69
+
70
+ WSGI_APPLICATION = 'rest_api.wsgi.application'
71
+
72
+
73
+ # Database
74
+ # https://docs.djangoproject.com/en/5.2/ref/settings/#databases
75
+
76
+ DATABASES = {
77
+ 'default': {
78
+ 'ENGINE': 'django.db.backends.sqlite3',
79
+ 'NAME': BASE_DIR / 'db.sqlite3',
80
+ }
81
+ }
82
+
83
+
84
+ # Password validation
85
+ # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
86
+
87
+ AUTH_PASSWORD_VALIDATORS = [
88
+ {
89
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
90
+ },
91
+ {
92
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
93
+ },
94
+ {
95
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
96
+ },
97
+ {
98
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
99
+ },
100
+ ]
101
+
102
+
103
+ # Internationalization
104
+ # https://docs.djangoproject.com/en/5.2/topics/i18n/
105
+
106
+ LANGUAGE_CODE = 'en-us'
107
+
108
+ TIME_ZONE = 'UTC'
109
+
110
+ USE_I18N = True
111
+
112
+ USE_TZ = True
113
+
114
+
115
+ # Static files (CSS, JavaScript, Images)
116
+ # https://docs.djangoproject.com/en/5.2/howto/static-files/
117
+
118
+ STATIC_URL = 'static/'
119
+
120
+ # Default primary key field type
121
+ # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
122
+
123
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
124
+
125
+
126
+ REST_FRAMEWORK = {
127
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
128
+ 'PAGE_SIZE': 10
129
+ }
130
+
@@ -0,0 +1,35 @@
1
+ """
2
+ URL configuration for server_rest_example project.
3
+
4
+ The `urlpatterns` list routes URLs to views. For more information please see:
5
+ https://docs.djangoproject.com/en/5.2/topics/http/urls/
6
+ Examples:
7
+ Function views
8
+ 1. Add an import: from my_app import views
9
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
10
+ Class-based views
11
+ 1. Add an import: from other_app.views import Home
12
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13
+ Including another URLconf
14
+ 1. Import the include() function: from django.urls import include, path
15
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16
+ """
17
+ from django.contrib import admin
18
+ from django.urls import path
19
+ from rest_framework import routers
20
+ from django.urls.conf import include
21
+ from rest_api.application.apps import django_application_inst
22
+ from holado_core.common.exceptions.technical_exception import TechnicalException
23
+
24
+ router = routers.DefaultRouter()
25
+ if django_application_inst is not None:
26
+ django_application_inst.register_urls(router)
27
+ print(f"router urls: {router.urls}")
28
+ else:
29
+ raise TechnicalException("Django application instance is not set")
30
+
31
+ urlpatterns = [
32
+ path('', include(router.urls)),
33
+ path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
34
+ path('admin/', admin.site.urls),
35
+ ]
@@ -0,0 +1,16 @@
1
+ """
2
+ WSGI config for server_rest_example project.
3
+
4
+ It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
8
+ """
9
+
10
+ import os
11
+
12
+ from django.core.wsgi import get_wsgi_application
13
+
14
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest_api.settings')
15
+
16
+ application = get_wsgi_application()
@@ -0,0 +1,110 @@
1
+
2
+ #################################################
3
+ # HolAdo (Holistic Automation do)
4
+ #
5
+ # (C) Copyright 2021-2025 by Eric Klumpp
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+ #
9
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
12
+ #################################################
13
+
14
+ from holado_core.common.exceptions.technical_exception import TechnicalException
15
+ import logging
16
+ from holado_core.common.tools.tools import Tools
17
+ import os
18
+ from holado_core.tools.abstracts.blocking_command_service import BlockingCommandService
19
+ from holado_system.system.command.command import CommandStates
20
+ from holado_core.common.handlers.wait import WaitFuncResultVerifying
21
+ from holado.common.handlers.object import DeleteableObject
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ try:
26
+ import django # @UnusedImport
27
+ with_django = True
28
+ except Exception as exc:
29
+ if Tools.do_log(logger, logging.DEBUG):
30
+ logger.debug(f"DjangoServer is not available. Initialization failed on error: {exc}")
31
+ with_django = False
32
+
33
+
34
+ class DjangoServer(DeleteableObject):
35
+ """
36
+ Django server
37
+ """
38
+
39
+ @classmethod
40
+ def is_available(cls):
41
+ return with_django
42
+
43
+ def __init__(self, name, django_project_path, port=8000, runserver_args=None):
44
+ super().__init__(name)
45
+
46
+ self.__django_project_path = django_project_path
47
+ self.__runserver_port = port
48
+ self.__runserver_args = runserver_args
49
+ self.__project_command = None
50
+
51
+ if not os.path.exists(django_project_path):
52
+ raise TechnicalException(f"Django project doesn't exist (project path: '{django_project_path}')")
53
+
54
+ def _delete_object(self):
55
+ if self.__project_command is not None and self.__project_command.status == CommandStates.Running:
56
+ self.stop()
57
+
58
+ @property
59
+ def django_project_path(self):
60
+ return self.__django_project_path
61
+
62
+ @property
63
+ def runserver_port(self):
64
+ return self.__runserver_port
65
+
66
+ @property
67
+ def runserver_args(self):
68
+ return self.__runserver_args
69
+
70
+ def start(self):
71
+ self.__start_server_by_command()
72
+
73
+ def __start_server_by_command(self):
74
+ self.__project_command = self._new_project_command()
75
+ self.__project_command.start()
76
+ self._wait_until_server_is_reachable()
77
+
78
+ def _new_project_command(self):
79
+ manage_path = os.path.join(self.django_project_path, "manage.py")
80
+ cmd = f"python {manage_path} runserver {self.runserver_port}"
81
+ if self.runserver_args:
82
+ cmd += f" {self.runserver_args}"
83
+
84
+ res = BlockingCommandService(f"Command running Django server '{self.name}'", cmd)
85
+ res.auto_stop = True
86
+
87
+ return res
88
+
89
+ def _wait_until_server_is_reachable(self):
90
+ import requests
91
+ url = f"http://127.0.0.1:{self.runserver_port}"
92
+ redo = WaitFuncResultVerifying("server is reachable",
93
+ lambda: requests.get(url),
94
+ lambda result: result and result.status_code == 200 )
95
+ redo.ignoring(Exception)
96
+ redo.polling_every(0.01)
97
+ redo.execute()
98
+
99
+ def stop(self):
100
+ if self.__project_command is None or self.__project_command.status != CommandStates.Running:
101
+ raise TechnicalException(f"Django server of project '{self.name}' is not running (status: {self.__project_command.status if self.__project_command else 'Unkown'})")
102
+ self.__project_command.stop()
103
+
104
+ def join(self, timeout=None):
105
+ self.__project_command.join(timeout)
106
+
107
+
108
+
109
+
110
+
@@ -0,0 +1,57 @@
1
+
2
+ #################################################
3
+ # HolAdo (Holistic Automation do)
4
+ #
5
+ # (C) Copyright 2021-2025 by Eric Klumpp
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+ #
9
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
12
+ #################################################
13
+
14
+ import logging
15
+ import os
16
+ from holado_core.tools.abstracts.blocking_command_service import BlockingCommandService
17
+ from holado_django.server.django_server import DjangoServer
18
+ from holado_core.common.handlers.wait import WaitFuncResultVerifying
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class GrpcDjangoServer(DjangoServer):
24
+ """
25
+ gRPC Django server
26
+ """
27
+
28
+ def __init__(self, name, django_project_path, runserver_args=None):
29
+ super().__init__(name, django_project_path, port=50051, runserver_args=runserver_args)
30
+
31
+ def _new_project_command(self):
32
+ manage_path = os.path.join(self.django_project_path, "manage.py")
33
+ cmd = f"python {manage_path} grpcrunserver"
34
+ if self.runserver_args:
35
+ cmd += f" {self.runserver_args}"
36
+
37
+ res = BlockingCommandService(f"Command running gRPC Django server '{self.name}'", cmd)
38
+ res.auto_stop = True
39
+
40
+ return res
41
+
42
+ def _wait_until_server_is_reachable(self):
43
+ import grpc_requests
44
+
45
+ endpoint = f"localhost:{self.runserver_port}"
46
+ def request_is_unimplemented():
47
+ try:
48
+ grpc_requests.Client.get_by_endpoint(endpoint)
49
+ except Exception as exc:
50
+ if "status = StatusCode.UNIMPLEMENTED" in str(exc):
51
+ return True
52
+ return False
53
+ redo = WaitFuncResultVerifying("server is reachable", request_is_unimplemented, lambda result: result )
54
+ redo.polling_every(0.01)
55
+ redo.execute()
56
+
57
+
@@ -0,0 +1,46 @@
1
+
2
+ #################################################
3
+ # HolAdo (Holistic Automation do)
4
+ #
5
+ # (C) Copyright 2021-2025 by Eric Klumpp
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+ #
9
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
12
+ #################################################
13
+
14
+
15
+ #################################################
16
+ #
17
+ # Patches are done to follow ITU recommendation:
18
+ # https://www.e-navigation.nl/sites/default/files/R-REC-M.1371-5-201402-I!!PDF-E_1.pdf
19
+ #
20
+ #################################################
21
+
22
+
23
+ import logging
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ # Since Django 4.1, requires_system_checks cannot be a boolean anymore.
29
+ # Framework djangogrpcframework is not maintained anymore.
30
+ # Patch it by removing reference to requires_system_checks
31
+ #
32
+ # USAGE:
33
+ # 1. If not using holado in django project, copy this file in django project
34
+ # 2. In manage.py, import this file
35
+
36
+ try:
37
+ import django_grpc_framework # @UnusedImport
38
+ with_django_grpc_framework = True
39
+ except:
40
+ with_django_grpc_framework = True
41
+
42
+ if with_django_grpc_framework:
43
+ from django_grpc_framework.management.commands.grpcrunserver import Command # @UnusedImport
44
+ del Command.requires_system_checks
45
+
46
+
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #################################################
4
+ # HolAdo (Holistic Automation do)
5
+ #
6
+ # (C) Copyright 2021-2025 by Eric Klumpp
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
13
+ #################################################
14
+
15
+
16
+ from .django_server_steps import *
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #################################################
4
+ # HolAdo (Holistic Automation do)
5
+ #
6
+ # (C) Copyright 2021-2025 by Eric Klumpp
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ # The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
13
+ #################################################
14
+
15
+ from holado.common.context.session_context import SessionContext
16
+ from holado_test.behave.behave import * # @UnusedWildImport
17
+ import logging
18
+ from holado_test.scenario.step_tools import StepTools
19
+ from holado_django.server.django_server import DjangoServer
20
+ from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
21
+ from holado_value.common.tables.converters.value_table_converter import ValueTableConverter
22
+ from holado_core.common.exceptions.technical_exception import TechnicalException
23
+ from holado_django.server.grpc_django_server import GrpcDjangoServer
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ def __get_session_context():
29
+ return SessionContext.instance()
30
+
31
+ def __get_scenario_context():
32
+ return __get_session_context().get_scenario_context()
33
+
34
+ def __get_text_interpreter():
35
+ return __get_scenario_context().get_text_interpreter()
36
+
37
+ def __get_variable_manager():
38
+ return __get_scenario_context().get_variable_manager()
39
+
40
+
41
+
42
+ @Given(r"(?P<var_name>{Variable}) = new Django server")
43
+ def step_impl(context, var_name):
44
+ var_name = StepTools.evaluate_variable_name(var_name)
45
+ table = BehaveStepTools.get_step_table(context)
46
+
47
+ kwargs = ValueTableConverter.convert_name_value_table_2_json_object(table)
48
+ if 'django_project_path' not in kwargs:
49
+ raise TechnicalException("Parameter 'django_project_path' is required")
50
+ if 'name' not in kwargs:
51
+ kwargs['name'] = f"Django server ({kwargs['django_project_path']})"
52
+
53
+ server = DjangoServer(**kwargs)
54
+
55
+ __get_variable_manager().register_variable(var_name, server)
56
+
57
+ @Given(r"(?P<var_name>{Variable}) = new gRPC Django server")
58
+ def step_impl(context, var_name):
59
+ var_name = StepTools.evaluate_variable_name(var_name)
60
+ table = BehaveStepTools.get_step_table(context)
61
+
62
+ kwargs = ValueTableConverter.convert_name_value_table_2_json_object(table)
63
+ if 'django_project_path' not in kwargs:
64
+ raise TechnicalException("Parameter 'django_project_path' is required")
65
+ if 'name' not in kwargs:
66
+ kwargs['name'] = f"Django server ({kwargs['django_project_path']})"
67
+
68
+ server = GrpcDjangoServer(**kwargs)
69
+
70
+ __get_variable_manager().register_variable(var_name, server)
71
+
72
+
73
+ @Step(r"start \(Django server: (?P<var_server>{Variable})\)")
74
+ def step_impl(context, var_server):
75
+ server = StepTools.evaluate_variable_value(var_server)
76
+ server.start()
77
+
78
+ @Step(r"stop \(Django server: (?P<var_server>{Variable})\)")
79
+ def step_impl(context, var_server):
80
+ server = StepTools.evaluate_variable_value(var_server)
81
+ server.stop()
82
+
83
+
@@ -49,18 +49,18 @@ def step_impl(context, var_name):
49
49
  var_name = StepTools.evaluate_variable_name(var_name)
50
50
 
51
51
  here = os.path.abspath(os.path.dirname(__file__))
52
- api_grpc_path = os.path.normpath(os.path.join(here, "..", "..", "..", "..", "..", "..", "..", "tests", "behave", "test_holado", "tools", "django", "api_grpc"))
53
- manage_path = os.path.join(api_grpc_path, "manage.py")
54
- cmd = f"python {manage_path} grpcrunserver"
52
+ django_project_path = os.path.normpath(os.path.join(here, "..", "..", "..", "..", "..", "..", "..", "tests", "behave", "test_holado", "tools", "django", "api_grpc"))
55
53
 
56
- obj = BlockingCommandService("internal gRPC server", cmd)
57
- obj.auto_stop = True
58
- obj.start()
59
-
60
- __get_variable_manager().register_variable(var_name, obj)
54
+ execute_steps(u"""
55
+ Given {var_name} = new gRPC Django server
56
+ | Name | Value |
57
+ | 'name' | 'gRPC server for holado tests' |
58
+ | 'django_project_path' | '{django_project_path}' |
59
+ When start (Django server: {var_name})
60
+ """.format(var_name=var_name, django_project_path=django_project_path))
61
61
 
62
62
  # Update imported grpc messages and services
63
- proto_path = os.path.join(api_grpc_path, "api_grpc", "api1", "proto")
63
+ proto_path = os.path.join(django_project_path, "api_grpc", "api1", "proto")
64
64
  __get_session_context().protobuf_messages.import_all_compiled_proto(proto_path)
65
65
  __get_session_context().grpc_services.import_all_compiled_proto(proto_path)
66
66
 
@@ -43,12 +43,7 @@ class HALogger(logging.Logger):
43
43
 
44
44
  if msg_size_limit is None:
45
45
  msg_size_limit = HALogger.default_message_size_limit
46
-
47
- if msg_size_limit is not None and msg_size_limit > 0 and len(msg) > msg_size_limit:
48
- truncated_suffix = f"[...({len(msg)-msg_size_limit})]"
49
- msg_to_log = Tools.truncate_text(msg, msg_size_limit, truncated_suffix)
50
- else:
51
- msg_to_log = msg
46
+ msg_to_log = Tools.truncate_text(msg, msg_size_limit)
52
47
 
53
48
  logging.Logger._log(self, level, msg_to_log, args, exc_info=exc_info, extra=extra, stack_info=stack_info, stacklevel=stacklevel)
54
49
 
@@ -22,6 +22,8 @@ from holado_core.common.tools.tools import Tools
22
22
  from holado_python.standard_library.typing import Typing
23
23
  from holado.common.handlers.undefined import default_context
24
24
  from holado.common.context.session_context import SessionContext
25
+ import psutil
26
+ import signal
25
27
 
26
28
  logger = logging.getLogger(__name__)
27
29
 
@@ -60,16 +62,40 @@ class MultitaskManager(object):
60
62
  if pid is None:
61
63
  return os.getppid()
62
64
  else:
63
- if GlobalSystem.get_os_type() == OSTypes.Linux:
64
- # Read /proc/<pid>/status and look for the line `PPid:\t120517\n`
65
- with open(f"/proc/{pid}/status", encoding="ascii") as f:
66
- for line in f.readlines():
67
- if line.startswith("PPid:\t"):
68
- return int(line[6:])
69
- raise TechnicalException(f"No PPid line found in /proc/{pid}/status")
70
- else:
71
- raise TechnicalException(f"Unmanaged OS type {GlobalSystem.get_os_type().name}")
72
-
65
+ process = psutil.Process(pid)
66
+ return process.ppid()
67
+
68
+ @classmethod
69
+ def get_children_process_ids(cls, pid=None, recursive=False):
70
+ if pid is None:
71
+ pid = os.getpid()
72
+
73
+ process = psutil.Process(pid)
74
+ children = process.children(recursive=recursive)
75
+ return [c.pid for c in children]
76
+
77
+ @classmethod
78
+ def kill_process(cls, pid, sig=signal.SIGTERM, do_kill_children=True, recursively=True):
79
+ try:
80
+ process = psutil.Process(pid)
81
+ except psutil.NoSuchProcess:
82
+ return
83
+
84
+ # Kill children
85
+ if do_kill_children:
86
+ children = process.children(recursive=recursively)
87
+ for proc in children:
88
+ try:
89
+ proc.send_signal(sig)
90
+ except signal.SIGTERM:
91
+ pass
92
+
93
+ # Kill process
94
+ try:
95
+ process.send_signal(sig)
96
+ except signal.SIGTERM:
97
+ pass
98
+
73
99
  @classmethod
74
100
  def has_thread_native_id(cls):
75
101
  from holado_multitask.multithreading.threadsmanager import ThreadsManager
@@ -22,20 +22,21 @@ from holado.common.handlers.undefined import undefined_argument, default_context
22
22
  from holado_core.common.exceptions.technical_exception import TimeoutTechnicalException
23
23
  from holado_core.common.exceptions.functional_exception import FunctionalException
24
24
  from holado_python.common.tools.datetime import DateTime, FORMAT_DATETIME_ISO
25
+ from holado.common.handlers.object import DeleteableObject
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
28
29
 
29
- class Thread(threading.Thread):
30
+ class Thread(threading.Thread, DeleteableObject):
30
31
  """ Helper class managing a thread."""
31
32
 
32
33
  def __init__(self, name, default_wait_timeout=None, register_thread=True, register_scope=default_context, delay_before_run_sec=None):
33
- super().__init__()
34
+ threading.Thread.__init__(self)
35
+ DeleteableObject.__init__(self, name)
34
36
 
35
37
  if default_wait_timeout is None:
36
38
  default_wait_timeout = Config.timeout_seconds * 1000
37
39
 
38
- self.__name = name
39
40
  # Note: if SessionContext doesn't have an instance (like during session context reset), use name+now as unique name
40
41
  self.__unique_name = SessionContext.instance().multitask_manager.prepare_thread(name) if SessionContext.has_instance() else f"{name}_{DateTime.datetime_2_str(DateTime.now(), FORMAT_DATETIME_ISO)}"
41
42
  self.__default_wait_timeout = default_wait_timeout
@@ -51,14 +52,15 @@ class Thread(threading.Thread):
51
52
 
52
53
  # Register thread
53
54
  # Note: Do not register thread if session context doesn't have an instance (like during session context reset)
55
+ self.__is_registered = False
56
+ self.__register_scope = None
54
57
  if register_thread and SessionContext.has_instance():
55
58
  # Registered name has to be unique
56
59
  SessionContext.instance().threads_manager.register_thread(self.__unique_name, self, scope=register_scope)
60
+ self.__is_registered = True
61
+ self.__register_scope = register_scope
62
+ self.on_delete_call_func(self.__unregister_thread)
57
63
 
58
- @property
59
- def name(self):
60
- return self.__name
61
-
62
64
  @property
63
65
  def unique_name(self):
64
66
  return self.__unique_name
@@ -149,6 +151,10 @@ class Thread(threading.Thread):
149
151
  elif wait_round_counter % 10 == 0:
150
152
  logger.debug("[{}] Still waiting {} (round {})".format(self.name, event_details, wait_round_counter))
151
153
  logger.debug("[{}] Finished waiting {}...".format(self.name, event_details))
154
+
155
+ def __unregister_thread(self):
156
+ if self.__is_registered:
157
+ SessionContext.instance().threads_manager.unregister_thread(self.__unique_name, self, scope=self.__register_scope)
152
158
 
153
159
 
154
160
  class InterruptableThread(Thread):
@@ -40,7 +40,10 @@ class Timer(InterruptableThread):
40
40
  self.finished.set()
41
41
 
42
42
  def run(self):
43
+ # Wait timer interval or canceled (ie finished==True)
43
44
  self.finished.wait(self.interval)
45
+
46
+ # Run function if not canceled
44
47
  if not self.finished.is_set():
45
48
  self.function(*self.args, **self.kwargs)
46
49
  self.finished.set()