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
@@ -158,26 +158,51 @@ class DateTime(object):
158
158
  res = dt_value.strftime(dt_format)
159
159
  return res
160
160
 
161
-
162
- ### Conversions to/from timestamp
161
+ @classmethod
162
+ def _get_datetime(cls, dt):
163
+ res = None
164
+ if dt is not None:
165
+ if isinstance(dt, datetime.datetime):
166
+ res = dt
167
+ elif isinstance(dt, str):
168
+ res = DateTime.str_2_datetime(dt)
169
+ else:
170
+ raise TechnicalException(f"Datetime can be a datetime, a str containing a datetime, or None")
171
+ return res
163
172
 
164
173
  @classmethod
165
174
  def _get_epoch_datetime(cls, epoch):
166
175
  res = None
167
176
  if epoch is not None:
168
- if isinstance(epoch, str):
177
+ if isinstance(epoch, datetime.datetime):
178
+ res = epoch
179
+ elif isinstance(epoch, str):
169
180
  if epoch in ['EPOCH_1970', 'EPOCH_JULIAN_CNES', 'EPOCH_JULIAN_NASA']:
170
181
  res = eval(epoch)
171
182
  else:
172
183
  res = DateTime.str_2_datetime(epoch)
173
- elif isinstance(epoch, datetime.datetime):
174
- res = epoch
175
184
  else:
176
185
  raise TechnicalException(f"Epoch can be a datetime, a str containing a datetime, a str containing an EPOCH name, or None")
177
186
  return res
178
187
 
188
+ @classmethod
189
+ def _get_timestamp(cls, ts):
190
+ res = None
191
+ if ts is not None:
192
+ if isinstance(ts, float) or isinstance(ts, int):
193
+ res = ts
194
+ elif isinstance(ts, str) and (Converter.is_float(ts) or Converter.is_integer(ts)):
195
+ res = Converter.to_float(ts)
196
+ else:
197
+ raise TechnicalException(f"Timestamp must be a float|int or a string containing a float|int (obtained type: {Typing.get_object_class_fullname(ts)})")
198
+ return res
199
+
200
+
201
+ ### Conversions to/from timestamp
202
+
179
203
  @classmethod
180
204
  def datetime_to_timestamp(cls, dt, epoch=None):
205
+ dt = cls._get_datetime(dt)
181
206
  epoch = cls._get_epoch_datetime(epoch)
182
207
 
183
208
  if epoch is None:
@@ -190,16 +215,9 @@ class DateTime(object):
190
215
  """
191
216
  Convert timestamp to UTC datetime
192
217
  """
218
+ ts = cls._get_timestamp(ts)
193
219
  epoch = cls._get_epoch_datetime(epoch)
194
220
 
195
- if isinstance(ts, str):
196
- if Converter.is_float(ts) or Converter.is_integer(ts):
197
- ts = Converter.to_float(ts)
198
- else:
199
- raise FunctionalException(f"Timestamp is a string that doesn't contain a float|int (obtained timestamp: '{ts}')")
200
- if not isinstance(ts, float) and not isinstance(ts, int):
201
- raise TechnicalException(f"Timestamp must be a float|int or a string containing a float|int (obtained type: {Typing.get_object_class_fullname(ts)})")
202
-
203
221
  res = datetime.datetime.fromtimestamp(ts, tz=datetime.timezone.utc)
204
222
  if epoch is not None:
205
223
  res = epoch + (res - EPOCH_1970)
@@ -2,20 +2,20 @@
2
2
  MIIDZTCCAk2gAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx
3
3
  CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZ0
4
4
  Y3BiaW4xDDAKBgNVBAsMA29wczETMBEGA1UEAwwKdGNwYmluLmNvbTEjMCEGCSqG
5
- SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwNjI2MTYxMzE3WhcN
6
- MjUwNjI3MTYxMzE3WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
- DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO4WKCcsF4eCjLUeUT3/3IbM8m0N
8
- Z5Fgmqe3K9lDOXFQa/dX98/gGPswQiWkJ3ALspYgDGnfqEQEc5kBrTNpom0xoWPD
9
- vgv7M4O222bMtVvXyvE5/Absw/VYGhW5KGln2k/4hEl0Xl2kS7P0KMlrczrjDyqW
10
- NhGMvgWfSSF1D1fP1tx4E/74TnCrUQy332wlprxB567kIlPLUe4zbNT1PvAhXXj1
11
- r9kKYBy6ontb+VWnrYraD3YhLOsez3OocY32JfvB8jFgjaxKTFMCchOJaFuFZEl8
12
- /wCPawwCdG8aIvC+EVuttbA3F/s3vDjQRTKW9WJ1KoJhRIwYbwwmvNIycMkCAwEA
13
- AaNCMEAwHQYDVR0OBBYEFMXW798gAD4iFP15opS+N49NX/9HMB8GA1UdIwQYMBaA
14
- FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQAtxMjai8tc
15
- VEA0TTPeDplTyDUYZqwnb+wbxOmb8+CTwt7hj/o1vDS82QGoKINiHyRGhyJrfiTQ
16
- 6TqBRxZMt52IdYwQoAMDnxyjxLdeVr0RNbxGziWBs3fjjahaEX/BMqzu5wd4mcpi
17
- UUmauULmYtY3vKiw8wsUEEkzzMM3fNJklMdMcR47cbH783FuJ7clhYaE/pJlq8RD
18
- wNIgume1u3BstRpIHafuZOy4Rg2nDiIuQoIORq7n8eRfgU9ZxVL7kZOgPFiTuokq
19
- 0EgnEhwOTUahO6aFpN9j/wCIANLrf1bZWw7v80bno7aFQ1XOm2RW5yJywkc7LFqi
20
- Dia/obmoNitb
5
+ SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwNzI4MTUyNDU5WhcN
6
+ MjUwNzI5MTUyNDU5WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
+ DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANUBILPvNb3OupTXJwW2AxwQhBWK
8
+ mvQlRHiDEcQM1EHnhH1DjANCSgPoV6ngaGa1Vt+p5Qe1uXc54eJ7y5yJ00i8Lg5c
9
+ L6t9fvhfjXCyWqh78XeL9jVTlwV9Bpz0BsfO91SSC7VEu/QA0syJGXTxdopnH0O0
10
+ /Wy9IX+SeZQzKscwk2G8ceVB2R5bi+48u3BE5UuzLsyZaVfzMS4zwHm2ADY3wj02
11
+ EmmTtQ0OMF8oxH1BP+UXwEX+Exen9i0VakBCgJqYzvy3tHrMZKylrVq5st46keSI
12
+ 7A666NMpEhMLI5kz25EDKlr7KVhim9jlfdUo1ASqN/7Bln158ycN0E6dObUCAwEA
13
+ AaNCMEAwHQYDVR0OBBYEFGrQ9rG00D/5IFX1xGLoKNNXMKn4MB8GA1UdIwQYMBaA
14
+ FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQACxSfDvc4P
15
+ osb9nP+UZH7B3D895N6OG1f1HQcBHu5V6FxiUSmLFetdUOwV3DeHUHkX2uQ6gWrt
16
+ EuhMI/BZQBI8tZvAe95Clfyxi0qGKoa0deyUdvAqdUoPGZJ9gAoSR/hb5e9zFjPk
17
+ p5hEpwZnbQsCABlSwc3oS74W5iuelOZmA6elR6CDEnvlNlX/0FYXzbyKrNtSfDFG
18
+ MgZv0lLSKkdtm0dgR7wgWmWf52ofG4hzDA9e1GtrAJ4SjVPy9hpOQu7syCNk8uCi
19
+ LKmXoJ1iVPB46QStpwNjLb6Tgre92py1QdKGsiV+26X+KQh1G4bcD6YZQ6EDXwq9
20
+ Hx7OyvVk0y8z
21
21
  -----END CERTIFICATE-----
@@ -1,28 +1,28 @@
1
1
  -----BEGIN PRIVATE KEY-----
2
- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDuFignLBeHgoy1
3
- HlE9/9yGzPJtDWeRYJqntyvZQzlxUGv3V/fP4Bj7MEIlpCdwC7KWIAxp36hEBHOZ
4
- Aa0zaaJtMaFjw74L+zODtttmzLVb18rxOfwG7MP1WBoVuShpZ9pP+IRJdF5dpEuz
5
- 9CjJa3M64w8qljYRjL4Fn0khdQ9Xz9bceBP++E5wq1EMt99sJaa8Qeeu5CJTy1Hu
6
- M2zU9T7wIV149a/ZCmAcuqJ7W/lVp62K2g92ISzrHs9zqHGN9iX7wfIxYI2sSkxT
7
- AnITiWhbhWRJfP8Aj2sMAnRvGiLwvhFbrbWwNxf7N7w40EUylvVidSqCYUSMGG8M
8
- JrzSMnDJAgMBAAECggEAB6aan7ehAe/GCcPxpGZmdcZ8O9jkbR2HmsAeHps0a1nE
9
- PmyLQBjZdKj2VzNROR6HHJ/nBjfztkvWUyJkqHRKjeu8XfJaVxQ5cZAcuM73PP5Q
10
- PTQ5zxcorOep3uzWSZzWvR8lUR72MVgoPp4n+WEEkO3e/OVa03GMYa3esb6LQa0S
11
- QcPkMwsxgPZQ1V9s85IIyVBn2ebbOhDbU1mz0FhzWtJ18Jikm0KhJuHUwwXAwO4H
12
- aINMbSkckOtyJwQ2Iu2Me8/ZiFLUTCMkdym2fb3jkWS+uiHxJye0Pg4AnXtIZE+W
13
- cUwwrZE+TnhodiqoAc3CgD0ZkqIpp07KMl7FM9jGaQKBgQD7vQHHBHKLFbW6e9dT
14
- ruZR+4kwOeTA1jzKlr2D8/Y6XJAPVG2UCn1oxfMTp6NkqC9nmalct4FH6lzpleZG
15
- Duwm2kxhmI+kxPj5MXH/MDlMSL3nw06mQD8tWUM9u6M73Rm6DMg5VMH3WqcUzycl
16
- 4FZDl0LcABDZsP+3f2A34l30uwKBgQDyHfxC5bA93cbvVzo3Qd5I611oKgg9J3vh
17
- /5I2bT3HKCIb7ruAthbDIO5/5K0ufIIzKAxcI9EJd2ViSO2MHhMQOfkrcKh6Kzb+
18
- u61r52ZRKN1aI2UiZzqtUTpkOetevRh1WMmH0CmItxZ/OpqDq/2OyQ6XedZWvd5t
19
- /PockMZaSwKBgBtZXWgRw5/4q9Wmvq4Iwl0FxtHGeGO64r/fwJclWgrdI4mG0qDS
20
- wu0vFEl+XPICk6Pdvdik1xbJD28RKgNSe7V84e94c6KjA6mPBaODybXP8VHMli7Z
21
- rANxPyzlxcYrLzBXUylnW+tTnfNUzhv/U1/kfw8HsszzvdRhskJgBXUhAoGBAIHU
22
- sHwS/PH/5fhcFvyglpkVlS4RM//PF3A2Auqqo7ETBU9jMiqv+f7CvwHX10IRRCQ4
23
- eoBCOIrR+oy8vJ0hV2mhHMs9iyqP19q7OyCcolItDp2SU14iauWbpCswn9Vaoy3x
24
- 4YexiUQloaux+j9XUA1sJSX3EIfNIuRp/piozaSHAoGAWODO4Ug0aU3SxHoj3muQ
25
- dgQmAcwNKif7UOWCcB/6qQFDMqDqiWuqPX1x7sUYjN61Dd2AwoOanI8phDsp2R4/
26
- 7ywBy2es5ePrk5Ma332QA/6rxkLVXGaGEV4mCVziF4PEGsbMZzx7YDIZoXUwuKw0
27
- JLVTXQhAmlEnNoMi1T/zLB8=
2
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVASCz7zW9zrqU
3
+ 1ycFtgMcEIQVipr0JUR4gxHEDNRB54R9Q4wDQkoD6Fep4GhmtVbfqeUHtbl3OeHi
4
+ e8ucidNIvC4OXC+rfX74X41wslqoe/F3i/Y1U5cFfQac9AbHzvdUkgu1RLv0ANLM
5
+ iRl08XaKZx9DtP1svSF/knmUMyrHMJNhvHHlQdkeW4vuPLtwROVLsy7MmWlX8zEu
6
+ M8B5tgA2N8I9NhJpk7UNDjBfKMR9QT/lF8BF/hMXp/YtFWpAQoCamM78t7R6zGSs
7
+ pa1aubLeOpHkiOwOuujTKRITCyOZM9uRAypa+ylYYpvY5X3VKNQEqjf+wZZ9efMn
8
+ DdBOnTm1AgMBAAECggEARaCzvv3P9HbSWPsnv18rDw57DsubMXnJMxetRAfpjo2O
9
+ qp/c8efGaBaYKWi41/IpLr3Lp0SJFuct5qoO+eG31kvlRj5uOsGwMqKRiqhSqEaz
10
+ vR9cYTws3tdqxP2kBcaq5NNEzoFkazOltMSQNMEFveJNvwU33kbI33nTElXTgv7O
11
+ EPo/+Xz4Vktk3F5yFHTQi+cIiql6F4QOHmKxMLKRFt85IJ2UCK+aJc1ehzXeYWcZ
12
+ YQbD4Xp0TpWopiv6YMJ+M+ibe+SjO9mA2nfEPn7s6DMEDRLNgxJjAkzuN3TACddY
13
+ 6jyyqVPN14axxWq2VMmmLPpMjBf4aaLDJPu07FlmVQKBgQD3lshvGvcgNBFlVjzB
14
+ DMYpfFyk2QUUrBgj1r0bpf19vvpRr69HufkhaBCOdMILJCYud1CM7nMwM27oOKeO
15
+ SGUGhaGBAAUa0eDgNyqxRX1lnClI7aUiAQlINoIBk6jb5qD62qnhHdoHCR4Ru8LY
16
+ wdRhpf5w+jxLW/+PwK1Pv6tOnwKBgQDcPZJVgnlysfYthwcPFjqkz1riHtx8hh3U
17
+ 8gYqa7yZWCenvSNQZphVCOiY0J7wzcDyDBZF+AAFQjJAlohC/YfnrjA0a6lr2bzq
18
+ tqWmGefoXVmK1JuOztnou4gSj27POY3EJTYhjaQ1hVuihp9Fo33/HmMzPfgQpC6/
19
+ s/f3eEvbKwKBgQCP4CtxhTX+nMNPJCIB/S5ahU5A7WwqaydMDEmwe3EnVYeZWIJ/
20
+ J+9CHnsgjXEFkGgvsF46x4ZgiEL255VW9XLfq2AC2sQcpcIMCYLhqQQJvAmxu4eu
21
+ jvOZ5zL9P5Vs1ETQAamei/5bAE1c+MNtupV0eUW22XOYR0nne4w7P6KI+QKBgH6G
22
+ WNULGd5MYpMRKXr/WD1qalnRRPW8ztKRH/2q2zR1MoLo46rC5eykK4vu/gB1E816
23
+ KCmWKdzbhKcaU7m4kSUGKudSmog9FIz61PsxksspJdHeBmfqacGMSsXu5Mfj1o/C
24
+ mPB5wMaGkHg0QrftDutLd83uYd6dk8XKDEB9OYb1AoGBAOzSZdMyFqhcks4y2UyO
25
+ UN7XXJojGPdYVRlQcVtlsYARqdqzKDzRQ0xmchU6y8JpqkPC700UvWrDHjLYJe3N
26
+ QBLk19dNseNEssiwESerWJVEk4QniGr2BnLboB3oWnUAvR+x5mVZJ1L9lXpFDmG5
27
+ QEuVAN4VEgS37CP5U7+5MzJi
28
28
  -----END PRIVATE KEY-----
@@ -47,7 +47,14 @@ if RMQClient.is_available():
47
47
 
48
48
  def close(self, reply_code=0, reply_text="Normal shutdown"):
49
49
  self.__is_closing = True
50
- super().close(reply_code=reply_code, reply_text=reply_text)
50
+ try:
51
+ super().close(reply_code=reply_code, reply_text=reply_text)
52
+ except ValueError as exc:
53
+ if self.__rapid_close and "file descriptor cannot be a negative integer" in str(exc):
54
+ # This exception can appear during channel close with rapid close option
55
+ pass
56
+ else:
57
+ raise exc
51
58
 
52
59
  def _flush_output(self, *waiters):
53
60
  if self.__rapid_close and self.__is_closing:
@@ -60,11 +67,18 @@ if RMQClient.is_available():
60
67
  """
61
68
  Override start_consuming method to add time_limit parameter.
62
69
  """
70
+ if Tools.do_log(logger, logging.DEBUG):
71
+ logger.debug(f"[{self}] Start consuming...")
63
72
  self.__is_consuming = True
64
73
  try:
65
74
  if not self.__rapid_close and time_limit is None and time_limit_on_stop is None:
66
75
  # Use pika implementation
76
+ if Tools.do_log(logger, logging.DEBUG):
77
+ logger.debug(f"[{self}] Start consuming with Pika implementation")
67
78
  return super().start_consuming()
79
+ else:
80
+ if Tools.do_log(logger, logging.DEBUG):
81
+ logger.debug(f"[{self}] Start consuming with HolAdo implementation")
68
82
 
69
83
  # Check if called from the scope of an event dispatch callback
70
84
  with self.connection._acquire_event_dispatch() as dispatch_allowed:
@@ -81,14 +95,22 @@ if RMQClient.is_available():
81
95
  if self.__is_stopping_consuming:
82
96
  t_limit = time_limit_on_stop
83
97
  if self.__rapid_close and t_limit is None:
84
- # To ensure a rapid close, time_limit shouldn't be None, set it to 0
98
+ # To ensure a rapid close, time_limit shouldn't be None, set it to 0 while stopping consuming
85
99
  t_limit = 0
86
100
  else:
87
101
  t_limit = time_limit
102
+ if t_limit is None and self.__rapid_close:
103
+ # To ensure a rapid close, time_limit shouldn't be None, set it to 0.1s as a compromise between rapidity and resource cost
104
+ t_limit = 0.1
105
+
106
+ if Tools.do_log(logger, logging.TRACE): # @UndefinedVariable
107
+ logger.trace(f"[{self}] Processing data events (time_limit={t_limit})")
88
108
  self._process_data_events(time_limit=t_limit)
89
109
  finally:
90
110
  self.__is_consuming = False
91
111
  self.__is_stopping_consuming = False
112
+ if Tools.do_log(logger, logging.DEBUG):
113
+ logger.debug(f"[{self}] Finished start consuming")
92
114
 
93
115
  def stop_consuming(self, consumer_tag=None):
94
116
  self.__is_stopping_consuming = True
@@ -562,12 +562,12 @@ class RMQConsumer(DeleteableObject):
562
562
  super().__init__(f"[{self.name}] Stop consuming on consumer {self.consumer_tag}")
563
563
 
564
564
  def _process(self2): # @NoSelf
565
- time.sleep(0.1)
566
- GcManager.collect(max_duration_until_next_collect_in_seconds=0)
567
565
  return self.is_consuming
568
566
  redo = StopRedo()
569
567
  redo.redo_while(True)
570
568
  redo.with_timeout(30*60) # Timeout of 30 min
569
+ redo.polling_every(0.01) # Wait 0.1 s beetween each try
570
+ redo.with_process_in_thread(False) # Run process in thread is not needed
571
571
  redo.execute()
572
572
 
573
573
  if Tools.do_log(logger, logging.DEBUG):
@@ -0,0 +1,129 @@
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
+ from holado_report.report.builders.report_builder import ReportBuilder
16
+ from holado_core.common.tools.tools import Tools
17
+ from holado_core.common.exceptions.technical_exception import TechnicalException
18
+ import json
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+
24
+ class FailureReportBuilder(ReportBuilder):
25
+ """Failure report builder
26
+ Supported formats: 'txt', 'json', 'xml'
27
+ """
28
+ def __init__(self, filepath, file_format='xml'):
29
+ if file_format not in ['txt', 'json', 'xml']:
30
+ raise TechnicalException(f"Unmanaged format '{file_format}' (possible formats: 'txt', 'json')")
31
+
32
+ self.__filepath = filepath
33
+ self.__file_format = file_format
34
+ self.__failures = {}
35
+
36
+ def build(self):
37
+ '''
38
+ The file is built after each scenario
39
+ '''
40
+ pass
41
+
42
+ def after_scenario(self, scenario, scenario_report=None):
43
+ from holado_report.report.report_manager import ReportManager
44
+ status_validation, step_failed, step_number = ReportManager.get_current_scenario_status_information(scenario)
45
+
46
+ if status_validation != "Passed":
47
+ step_error_message = ReportManager.get_step_error_message(step_failed).strip()
48
+ if step_error_message not in self.__failures:
49
+ self.__failures[step_error_message] = []
50
+
51
+ if self.__file_format == 'txt':
52
+ msg_list = []
53
+ msg_list.append(f"scenario in {scenario.filename} at l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}")
54
+ msg_list.append(f" Feature/Scenario: {scenario.feature.name} => {scenario.name}")
55
+ msg_list.append(f" Report: {scenario_report.report_path}")
56
+ msg_list.append(f" Tags: -t " + " -t ".join(scenario.feature.tags + scenario.tags))
57
+ msg_scenario = "\n".join(msg_list)
58
+
59
+ self.__failures[step_error_message].append(msg_scenario)
60
+ elif self.__file_format in ['json', 'xml']:
61
+ scenario_info = {
62
+ 'title': f"{scenario.filename} - l.{scenario.line} - step {step_number} (l.{step_failed.line}) - {status_validation}",
63
+ 'scenario': f"{scenario.feature.name} => {scenario.name}",
64
+ 'report': scenario_report.report_path,
65
+ 'tags': "-t " + " -t ".join(scenario.feature.tags + scenario.tags),
66
+ }
67
+ self.__failures[step_error_message].append(scenario_info)
68
+ else:
69
+ raise TechnicalException(f"Unmanaged format '{self.__file_format}' (possible formats: 'txt', 'json')")
70
+
71
+ self.__update_file()
72
+
73
+ def __update_file(self):
74
+ with open(self.__filepath, "wt") as fout:
75
+ if self.__file_format == 'txt':
76
+ for failure, scenarios_messages in self.__failures.items():
77
+ fout.write(failure + "\n")
78
+ fout.write("\n")
79
+ for msg in scenarios_messages:
80
+ fout.write(Tools.indent_string(4, msg) + "\n")
81
+ fout.write("\n")
82
+ elif self.__file_format == 'json':
83
+ json_str = json.dumps(self.__failures, ensure_ascii=False, indent=4)
84
+ fout.write(json_str)
85
+ elif self.__file_format == 'xml':
86
+ self.__file_write_failures(fout, self.__failures, sort_by_nb_scenario=True)
87
+ else:
88
+ raise TechnicalException(f"Unmanaged format '{self.__file_format}' (possible formats: 'txt', 'json')")
89
+
90
+ def __file_write_failures(self, fout, __failures, sort_by_nb_scenario=True):
91
+ fout.write("<failures>\n")
92
+
93
+ if sort_by_nb_scenario:
94
+ for error_message, scenarios in sorted(self.__failures.items(), key=lambda item:-len(item[1])):
95
+ self.__file_write_failure(fout, error_message, scenarios)
96
+ else:
97
+ for error_message, scenarios in self.__failures.items():
98
+ self.__file_write_failure(fout, error_message, scenarios)
99
+
100
+ fout.write("</failures>\n")
101
+
102
+ def __file_write_failure(self, fout, error_message, scenarios):
103
+ msg_list = []
104
+
105
+ msg_list.append(" <failure>")
106
+
107
+ if "\n" in error_message:
108
+ msg_list.append(f" <error_message>")
109
+ msg_list.append(Tools.indent_string(12, error_message))
110
+ msg_list.append(f" </error_message>")
111
+ else:
112
+ msg_list.append(f" <error_message>{error_message}</error_message>")
113
+
114
+ msg_list.append(f" <scenarios>")
115
+ for scenario_info in scenarios:
116
+ msg_list.append(f" <scenario>")
117
+ for key, value in scenario_info.items():
118
+ msg_list.append(f" <{key}>{value}</{key}>")
119
+ msg_list.append(f" </scenario>")
120
+ msg_list.append(f" </scenarios>")
121
+
122
+ msg_list.append(" </failure>")
123
+
124
+ msg = "\n".join(msg_list) + "\n"
125
+ fout.write(msg)
126
+
127
+
128
+
129
+
@@ -23,6 +23,7 @@ from holado_report.report.builders.short_scenario_failed_report_builder import S
23
23
  from holado_report.report.reports.base_report import BaseReport
24
24
  from holado_scripting.common.tools.evaluate_parameters import EvaluateParameters
25
25
  from holado_report.report.builders.summary_scenario_report_builder import SummaryScenarioReportBuilder
26
+ from holado_report.report.builders.failure_report_builder import FailureReportBuilder
26
27
  # from holado_core.scenario.scenario_duration_manager import ScenarioDurationManager
27
28
 
28
29
  logger = logging.getLogger(__name__)
@@ -65,6 +66,9 @@ class ReportManager(BaseReport):
65
66
  fn = self.get_path("report_summary_scenario_all.txt")
66
67
  self.add_report_builder(SummaryScenarioReportBuilder(fn))
67
68
 
69
+ fn = self.get_path("report_failures.xml")
70
+ self.add_report_builder(FailureReportBuilder(fn))
71
+
68
72
  fn = self.get_path("report_short_scenario_failed.txt")
69
73
  self.add_report_builder(ShortScenarioFailedReportBuilder(fn))
70
74
 
@@ -1,2 +1,2 @@
1
- Client with library "requests"
2
1
  Client with library "httpx"
2
+
@@ -16,6 +16,8 @@ from holado_core.common.exceptions.technical_exception import TechnicalException
16
16
  import logging
17
17
  from holado_core.common.tools.tools import Tools
18
18
  import json
19
+ from holado_core.common.tools.converters.converter import Converter
20
+ from holado_json.ipc.json_converter import JsonConverter
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -116,17 +118,27 @@ class RestClient(object):
116
118
  if self.__headers:
117
119
  res['headers'] = self.__headers
118
120
  if request_kwargs:
119
- # Ensure data is in json string format
120
- if 'data' in request_kwargs:
121
- data = request_kwargs.pop('data')
122
- if data is not None and not isinstance(data, str):
123
- data = json.dumps(data)
124
- res['data'] = data
125
-
121
+ # Add all kwargs except data
122
+ data = request_kwargs.pop('data', None)
126
123
  for key in list(request_kwargs.keys()):
127
124
  if key in res and isinstance(res[key], dict):
128
125
  res[key].update(request_kwargs.pop(key))
129
126
  res.update(request_kwargs)
127
+
128
+ # Ensure data is in right format
129
+ if data is not None:
130
+ # Convert data to json object for types not supported by requests.request
131
+ if not (isinstance(data, str) or Converter.is_dict(data) or Converter.is_list(data) or isinstance(data, bytes) or Converter.is_file_like(data)):
132
+ converter = JsonConverter()
133
+ data = converter.to_json(data)
134
+
135
+ # Convert data to string for some content types
136
+ if not isinstance(data, str):
137
+ content_type = res['headers']['Content-Type'] if 'headers' in res and 'Content-Type' in res['headers'] else None
138
+ if content_type is not None and 'application/json' in content_type:
139
+ data = json.dumps(data)
140
+ res['data'] = data
141
+
130
142
  return res
131
143
 
132
144
 
@@ -112,8 +112,7 @@ if RestClient.is_available():
112
112
  # Manage request arguments as step table
113
113
  if hasattr(context, "table") and context.table is not None:
114
114
  table = BehaveStepTools.convert_step_table_2_value_table_with_header(context.table)
115
- json_obj = ValueTableConverter.convert_name_value_table_2_json_object(table)
116
- data = json.dumps(json_obj)
115
+ data = ValueTableConverter.convert_name_value_table_2_json_object(table)
117
116
 
118
117
  # Manage request arguments as json text
119
118
  # Manage data as json text
@@ -17,6 +17,7 @@ from holado_test.behave.behave import * # @UnusedWildImport
17
17
  import os.path
18
18
  import logging
19
19
  from holado_test.scenario.step_tools import StepTools
20
+ from holado_django.server.django_server import DjangoServer
20
21
 
21
22
  logger = logging.getLogger(__name__)
22
23
 
@@ -40,22 +41,19 @@ def step_impl(context, var_name):
40
41
  var_name = StepTools.evaluate_variable_name(var_name)
41
42
 
42
43
  here = os.path.abspath(os.path.dirname(__file__))
43
- manage_path = os.path.normpath(os.path.join(here, "..", "..", "tools", "django", "api_rest", "manage.py"))
44
- cmd = f"python {manage_path} runserver"
44
+ django_project_path = os.path.normpath(os.path.join(here, "..", "..", "..", "..", "..", "..", "..", "tests", "behave", "test_holado", "tools", "django", "api_rest"))
45
45
 
46
46
  execute_steps(u"""
47
- Given {var_name} = new command '{cmd}' with
48
- | Name | Value |
49
- | 'auto_stop' | True |
50
- | 'blocking' | True |
51
- | 'name' | 'internal REST server' |
52
- Given run command {var_name}
53
- Then command {var_name} has status Ready
54
- When wait 5 seconds
55
- Then command {var_name} has status Running
56
- """.format(cmd=cmd, var_name=var_name))
57
-
47
+ Given {var_name} = new Django server
48
+ | Name | Value |
49
+ | 'name' | 'REST server for holado tests' |
50
+ | 'django_project_path' | '{django_project_path}' |
51
+ | 'port' | 8000 |
52
+ When start (Django server: {var_name})
53
+ """.format(var_name=var_name, django_project_path=django_project_path))
58
54
 
55
+
56
+
59
57
  @Given(r"(?P<var_name>{Variable}) = new internal REST client")
60
58
  def step_impl(context, var_name):
61
59
  var_name = StepTools.evaluate_variable_name(var_name)
@@ -20,6 +20,9 @@ import time
20
20
  from enum import IntEnum
21
21
  from holado_core.common.exceptions.functional_exception import FunctionalException
22
22
  import copy
23
+ import signal
24
+ from holado_core.common.tools.tools import Tools
25
+ from holado_multitask.multitasking.multitask_manager import MultitaskManager
23
26
 
24
27
  logger = logging.getLogger(__name__)
25
28
 
@@ -31,6 +34,7 @@ class CommandStates(IntEnum):
31
34
  Success = 3
32
35
  Error = 4
33
36
 
37
+
34
38
  class Command(threading.Thread):
35
39
  """
36
40
  Execute a command in a thread.
@@ -58,6 +62,7 @@ class Command(threading.Thread):
58
62
  self.__callback_delay_ms = None
59
63
  self.__external_parameters = {}
60
64
  self.__subprocess_kwargs = subprocess_kwargs
65
+ self.__stop_signal = signal.SIGTERM
61
66
 
62
67
 
63
68
  @property
@@ -94,29 +99,38 @@ class Command(threading.Thread):
94
99
  return self.__process.returncode
95
100
  else:
96
101
  return None
97
-
102
+
98
103
  @property
99
104
  def callback(self):
100
105
  return self.__callback
101
-
106
+
102
107
  @callback.setter
103
108
  def callback(self, callback):
104
109
  """Set callback called when execution end."""
105
110
  self.__callback = callback
106
-
111
+
107
112
  @property
108
113
  def callback_delay_ms(self):
109
114
  return self.__callback_delay_ms
110
-
115
+
111
116
  @callback_delay_ms.setter
112
117
  def callback_delay_ms(self, delay_ms):
113
118
  """Set callback delay in ms."""
114
119
  self.__callback_delay_ms = delay_ms
115
-
120
+
116
121
  @property
117
122
  def external_parameters(self):
118
123
  return self.__external_parameters
119
-
124
+
125
+ @property
126
+ def stop_signal(self):
127
+ return self.__stop_signal
128
+
129
+ @stop_signal.setter
130
+ def stop_signal(self, stop_signal):
131
+ """Set stop signal."""
132
+ self.__stop_signal = stop_signal
133
+
120
134
  def run(self):
121
135
  logger.debug("Call command: {}".format(self.cmd))
122
136
  try:
@@ -139,7 +153,7 @@ class Command(threading.Thread):
139
153
 
140
154
  self.__stdout = ""
141
155
  self.__stderr = ""
142
- while (self.__process.returncode is None):
156
+ while self.__process.returncode is None:
143
157
  # Wait a small time and in same time yield this thread
144
158
  time.sleep(0.001)
145
159
 
@@ -175,7 +189,7 @@ class Command(threading.Thread):
175
189
  logger.debug(f"Command [{self.cmd}] has finished, calling callback...")
176
190
  self.callback(self)
177
191
  elif self.error is not None:
178
- logger.error(f"Command [{self.cmd}] has finished on error : {self.error}")
192
+ logger.error(f"Command [{self.cmd}] has finished on error: {self.error}")
179
193
  elif self.state == CommandStates.Error:
180
194
  logger.error(f"Command [{self.cmd}] has finished on error code {self.return_code} and stderr: {self.stderr}")
181
195
  elif self.state != CommandStates.Success:
@@ -208,13 +222,21 @@ class Command(threading.Thread):
208
222
  raise FunctionalException(msg)
209
223
 
210
224
  def kill(self):
225
+ # Note: kill is equivalent to terminate in subprocess implementation
211
226
  if self.state == CommandStates.Running:
212
227
  self.__process.kill()
213
228
 
214
229
  def terminate(self):
215
230
  if self.state == CommandStates.Running:
216
231
  self.__process.terminate()
217
-
232
+
233
+ def stop(self):
234
+ if self.state == CommandStates.Running:
235
+ if Tools.do_log(logger, logging.DEBUG):
236
+ logger.debug(f"Stopping command [{self.cmd}] with signal {self.__stop_signal} and PID {self.__process.pid}")
237
+
238
+ MultitaskManager.kill_process(self.__process.pid, sig=self.__stop_signal, do_kill_children=True, recursively=True)
239
+
218
240
  def __repr__(self):
219
241
  return pprint.pformat({'cmd' : self.cmd,
220
242
  'is alive' : self.is_alive() })
@@ -14,9 +14,7 @@
14
14
  import logging
15
15
  from holado.common.handlers.enums import AutoNumber
16
16
  import platform
17
- from holado_system.system.command.command import Command, CommandStates
18
17
  from holado_core.common.exceptions.technical_exception import TechnicalException
19
- from holado_system.system.command.command_result import CommandResult
20
18
  import time
21
19
  import os
22
20
  from holado_core.common.tools.tools import Tools
@@ -87,6 +85,9 @@ class GlobalSystem(object):
87
85
 
88
86
  @classmethod
89
87
  def execute_command(cls, cmd, do_log_output=False, do_raise_on_stderr=False):
88
+ from holado_system.system.command.command import Command, CommandStates
89
+ from holado_system.system.command.command_result import CommandResult
90
+
90
91
  command = Command(cmd, do_log_output=do_log_output, do_raise_on_stderr=do_raise_on_stderr)
91
92
  command.start()
92
93
  command.join()
@@ -183,5 +184,4 @@ class GlobalSystem(object):
183
184
  return port
184
185
  return None
185
186
 
186
-
187
187
 
@@ -19,10 +19,12 @@ import logging
19
19
 
20
20
  # Add testing solution sources paths
21
21
  here = os.path.abspath(os.path.dirname(__file__))
22
- sys.path.append(here)
23
- holado_path = os.path.normpath(os.path.join(here, "..", "..", ".."))
24
- sys.path.append( os.path.join(holado_path, "src") )
25
- sys.path.append( os.path.join(holado_path, "tests", "behave") )
22
+ sys.path.insert(0, here)
23
+
24
+ # Add HolAdo source paths (needed when using a clone of HolAdo project)
25
+ from initialize_holado import insert_holado_source_paths
26
+ insert_holado_source_paths()
27
+
26
28
 
27
29
  # Configure HolAdo
28
30
  import holado