jaaql-middleware-python 4.33.19__tar.gz → 4.33.20__tar.gz

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.
Files changed (77) hide show
  1. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/PKG-INFO +1 -1
  2. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/constants.py +1 -1
  3. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/email/email_manager_service.py +153 -31
  4. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql_middleware_python.egg-info/PKG-INFO +1 -1
  5. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/LICENSE.txt +0 -0
  6. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/README.md +0 -0
  7. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/__init__.py +0 -0
  8. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/config/__init__.py +0 -0
  9. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/config/config-docker.ini +0 -0
  10. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/config/config-test.ini +0 -0
  11. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/config/config.ini +0 -0
  12. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/config_constants.py +0 -0
  13. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/db/__init__.py +0 -0
  14. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/db/db_interface.py +0 -0
  15. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/db/db_pg_interface.py +0 -0
  16. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/db/db_utils.py +0 -0
  17. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/db/db_utils_no_circ.py +0 -0
  18. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/documentation/__init__.py +0 -0
  19. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/documentation/documentation_internal.py +0 -0
  20. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/documentation/documentation_public.py +0 -0
  21. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/documentation/documentation_shared.py +0 -0
  22. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/email/__init__.py +0 -0
  23. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/email/email_manager.py +0 -0
  24. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/email/patch_ems.py +0 -0
  25. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/exceptions/__init__.py +0 -0
  26. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/exceptions/custom_http_status.py +0 -0
  27. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/exceptions/http_status_exception.py +0 -0
  28. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/exceptions/jaaql_interpretable_handled_errors.py +0 -0
  29. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/exceptions/not_yet_implement_exception.py +0 -0
  30. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/generated_constants.py +0 -0
  31. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/interpreter/__init__.py +0 -0
  32. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/interpreter/interpret_jaaql.py +0 -0
  33. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/jaaql.py +0 -0
  34. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/migrations/__init__.py +0 -0
  35. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/migrations/migrations.py +0 -0
  36. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/__init__.py +0 -0
  37. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/base_controller.py +0 -0
  38. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/base_model.py +0 -0
  39. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/controller.py +0 -0
  40. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/controller_interface.py +0 -0
  41. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/exception_queries.py +0 -0
  42. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/generated_queries.py +0 -0
  43. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/handmade_queries.py +0 -0
  44. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/model.py +0 -0
  45. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/model_interface.py +0 -0
  46. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/mvc/response.py +0 -0
  47. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/openapi/__init__.py +0 -0
  48. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/openapi/swagger_documentation.py +0 -0
  49. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/patch.py +0 -0
  50. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/01.install_domains.generated.sql +0 -0
  51. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/02.install_super_user.exceptions.sql +0 -0
  52. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/03.install_super_user.handwritten.sql +0 -0
  53. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/04.install_jaaql_data_structures.generated.sql +0 -0
  54. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/05.install_static_data.generated.sql +0 -0
  55. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/06.install_jaaql.exceptions.sql +0 -0
  56. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/ZZZZ.generated_functions_views_and_permissions.sql +0 -0
  57. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/ZZZZ.reset_references.sql +0 -0
  58. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/scripts/swagger_template.html +0 -0
  59. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/__init__.py +0 -0
  60. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/cached_canned_query_service.py +0 -0
  61. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/migrations_manager_service.py +0 -0
  62. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/patch_mms.py +0 -0
  63. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/patch_shared_var_service.py +0 -0
  64. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/services/shared_var_service.py +0 -0
  65. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/__init__.py +0 -0
  66. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/cron.py +0 -0
  67. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/crypt_utils.py +0 -0
  68. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/options.py +0 -0
  69. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/utils.py +0 -0
  70. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/utils_no_project_imports.py +0 -0
  71. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql/utilities/vault.py +0 -0
  72. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql_middleware_python.egg-info/SOURCES.txt +0 -0
  73. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql_middleware_python.egg-info/dependency_links.txt +0 -0
  74. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql_middleware_python.egg-info/requires.txt +0 -0
  75. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/jaaql_middleware_python.egg-info/top_level.txt +0 -0
  76. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/setup.cfg +0 -0
  77. {jaaql_middleware_python-4.33.19 → jaaql_middleware_python-4.33.20}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaaql-middleware-python
3
- Version: 4.33.19
3
+ Version: 4.33.20
4
4
  Summary: The jaaql package, allowing for rapid development and deployment of RESTful HTTP applications
5
5
  Home-page: https://github.com/JAAQL/JAAQL-middleware-python
6
6
  Author: Software Quality Measurement and Improvement bv
@@ -208,5 +208,5 @@ ROLE__postgres = "postgres"
208
208
 
209
209
  PROTOCOL__postgres = "postgresql://"
210
210
 
211
- VERSION = "4.33.19"
211
+ VERSION = "4.33.20"
212
212
 
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import traceback
2
3
  import uuid
3
4
  import time
@@ -132,6 +133,10 @@ class DrivenChrome:
132
133
  options.add_argument("--disable-extensions")
133
134
  options.add_argument("--headless=new")
134
135
 
136
+ if os.environ.get("IGNORE_CERTS") == "TRUE":
137
+ print("Chrome starting with --ignore-certificate-errors (IGNORE_CERTS=TRUE)")
138
+ options.add_argument("--ignore-certificate-errors")
139
+
135
140
  # Add options to handle long-running JavaScript
136
141
  options.add_argument("--disable-background-timer-throttling")
137
142
  options.add_argument("--disable-renderer-backgrounding")
@@ -212,6 +217,8 @@ class DrivenChrome:
212
217
  def chrome_page_to_pdf(self, url: str, access_token: str, parameters: dict, document_id: str):
213
218
  with self.chrome_lock:
214
219
  try:
220
+ self.last_progress_log = None
221
+
215
222
  origin = "{uri.scheme}://{uri.netloc}".format(uri=urlparse(url))
216
223
  self.driver.execute_cdp_cmd(
217
224
  "Storage.clearDataForOrigin",
@@ -301,10 +308,31 @@ class DrivenChrome:
301
308
 
302
309
  except TimeoutException:
303
310
  elapsed = time_delta_ms(start_time, datetime.now()) / 1000
304
- # Log any console errors before failing
305
311
  print(f"Timeout after {elapsed:.1f}s waiting for document {document_id}")
312
+
313
+ try:
314
+ print_url = self.driver.current_url
315
+ if not CHROME_DEBUGGING:
316
+ print_url = print_url.split("oauth_token=")[0]
317
+ print("Current URL:", print_url)
318
+
319
+ # Dump HTML for inspection
320
+ html_path = f"/tmp/render_failure_{document_id}.html"
321
+ with open(html_path, "w", encoding="utf-8") as f:
322
+ f.write(self.driver.page_source)
323
+ print(f"Wrote failing page HTML to {html_path}")
324
+
325
+ # Screenshot
326
+ png_path = f"/tmp/render_failure_{document_id}.png"
327
+ self.driver.save_screenshot(png_path)
328
+ print(f"Wrote failing page screenshot to {png_path}")
329
+
330
+ except Exception as dump_ex:
331
+ print(f"Error while dumping failure artefacts: {dump_ex}")
332
+
306
333
  for log in self.driver.get_log('browser'):
307
334
  print(f"CHROMEFAILURE: {log if isinstance(log, str) else json.dumps(log)}")
335
+
308
336
  raise Exception(f"Render timeout after {elapsed:.1f}s (limit: {self.render_timeout}s)")
309
337
 
310
338
  # Process filename
@@ -396,73 +424,167 @@ class DrivenChrome:
396
424
 
397
425
  def render_document_requests(self):
398
426
  consecutive_failures = 0
399
- max_consecutive_failures = 5
400
427
  last_resp = None
401
428
 
429
+ # Per-document attempt tracking
430
+ doc_failures = {} # { document_id (str): int }
431
+ max_attempts_per_doc = 3 # 1 + 1 + (restart) + 1 final
432
+ restart_after_attempt = 2 # restart Chrome after 2nd timeout
433
+
402
434
  while True:
403
435
  resp = None
404
436
 
405
437
  try:
438
+ # Re-use the same response only if we've explicitly chosen to retry it
406
439
  resp = last_resp if last_resp is not None else execute_supplied_statement_singleton(
407
- self.db_interface, QUERY__fetch_unrendered_document,
440
+ self.db_interface,
441
+ QUERY__fetch_unrendered_document,
408
442
  as_objects=True,
409
443
  decrypt_columns=[KEY__parameters, KEY__oauth_token],
410
- encryption_key=self.db_key)
444
+ encryption_key=self.db_key
445
+ )
411
446
  last_resp = None
447
+
412
448
  parameters = {}
413
449
  if resp[KEY__parameters]:
414
450
  parameters = json.loads(resp[KEY__parameters])
415
451
 
416
- base_url = EmailAttachment.static_format_attached_url(resp[KG__application__base_url] + "/" + resp["url"], self.is_deployed)
452
+ document_id = str(resp[KEY__document_id])
417
453
 
418
- filename, content = self.chrome_page_to_pdf(base_url, resp[KEY__oauth_token], parameters, str(resp[KEY__document_id]))
454
+ base_url = EmailAttachment.static_format_attached_url(
455
+ resp[KG__application__base_url] + "/" + resp["url"],
456
+ self.is_deployed
457
+ )
458
+
459
+ filename, content = self.chrome_page_to_pdf(
460
+ base_url,
461
+ resp[KEY__oauth_token],
462
+ parameters,
463
+ document_id
464
+ )
465
+
466
+ inputs = {
467
+ KEY__document_id: resp[KEY__document_id],
468
+ KEY__content: None,
469
+ KG__document_request__file_name: filename
470
+ }
419
471
 
420
- inputs = {KEY__document_id: resp[KEY__document_id], KEY__content: None, KG__document_request__file_name: filename}
421
472
  if resp[KEY__create_file]:
422
473
  if not os.path.exists(self.template_dir_path):
423
474
  os.mkdir(self.template_dir_path)
424
- with open(os.path.join(self.template_dir_path,
425
- str(resp[KEY__document_id]) + "." + resp[KEY__render_as]), "wb") as f:
475
+ with open(
476
+ os.path.join(
477
+ self.template_dir_path,
478
+ str(resp[KEY__document_id]) + "." + resp[KEY__render_as]
479
+ ),
480
+ "wb"
481
+ ) as f:
426
482
  f.write(content)
427
483
  else:
428
484
  inputs[KEY__content] = content
429
485
 
430
- execute_supplied_statement(self.db_interface, QUERY__mark_rendered_document_completed, inputs)
486
+ execute_supplied_statement(
487
+ self.db_interface,
488
+ QUERY__mark_rendered_document_completed,
489
+ inputs
490
+ )
431
491
 
492
+ # Success: reset counters for this document
432
493
  consecutive_failures = 0
494
+ doc_failures.pop(document_id, None)
495
+
433
496
  except HttpStatusException as ex:
434
- consecutive_failures = 0 # This is a properly handled exception
497
+ # Application-level error, not a Chrome/timeout error
498
+ consecutive_failures = 0
499
+
500
+ document_id = None
501
+ if resp is not None and KEY__document_id in resp:
502
+ document_id = str(resp[KEY__document_id])
435
503
 
436
504
  if ex.response_code == HTTPStatus.UNPROCESSABLE_ENTITY:
437
- # When there are no rendered documents, we end up here with a null resp
505
+ # "No work" case, or doc no longer valid
438
506
  if resp is not None:
439
- execute_supplied_statement(self.db_interface,
507
+ execute_supplied_statement(
508
+ self.db_interface,
440
509
  QUERY__mark_rendered_document_completed_with_error,
441
- { KEY__document_id: resp[KEY__document_id] })
442
-
510
+ {KEY__document_id: resp[KEY__document_id]}
511
+ )
443
512
  time.sleep(0.25)
444
513
  else:
514
+ # Some other app-level error: give up on this document
445
515
  traceback.print_exc()
516
+ if document_id is not None:
517
+ try:
518
+ execute_supplied_statement(
519
+ self.db_interface,
520
+ QUERY__mark_rendered_document_completed_with_error,
521
+ {KEY__document_id: resp[KEY__document_id]}
522
+ )
523
+ except Exception:
524
+ traceback.print_exc()
525
+ doc_failures.pop(document_id, None)
526
+
446
527
  except Exception as ex:
447
528
  consecutive_failures += 1
448
529
  print(f"Error in render_document_requests: {ex}")
449
530
 
450
- if consecutive_failures == 1:
451
- # We will now retry the same resp
452
- last_resp = resp
453
-
454
- if consecutive_failures >= max_consecutive_failures:
455
- print(f"Too many consecutive failures ({consecutive_failures}), attempting Chrome restart")
456
- try:
457
- self.restart_chrome(reason=f"{consecutive_failures} consecutive failures")
458
- consecutive_failures = 0 # Reset after restart
459
- except Exception as restart_error:
460
- print(f"Failed to restart Chrome: {restart_error}")
461
- # Sleep longer if restart fails
462
- time.sleep(30)
463
- else:
464
- # Regular error handling
465
- time.sleep(min(consecutive_failures * 2, 10))
531
+ document_id = None
532
+ if resp is not None and KEY__document_id in resp:
533
+ document_id = str(resp[KEY__document_id])
534
+
535
+ if document_id is not None:
536
+ # Track attempts for this specific document
537
+ doc_failures[document_id] = doc_failures.get(document_id, 0) + 1
538
+ attempt_no = doc_failures[document_id]
539
+
540
+ # Only treat these as "Chrome problems" if they are timeouts
541
+ is_timeout = "Render timeout after" in str(ex)
542
+
543
+ if attempt_no < max_attempts_per_doc:
544
+ # 1st failure: retry once with same Chrome
545
+ if attempt_no == 1:
546
+ last_resp = resp
547
+
548
+ # 2nd failure *and* it's a timeout: restart Chrome, then retry once more
549
+ elif attempt_no == restart_after_attempt and is_timeout:
550
+ print(
551
+ f"Document {document_id} has timed out twice, "
552
+ "restarting Chrome then trying once more"
553
+ )
554
+ try:
555
+ self.restart_chrome(
556
+ reason=f"document {document_id} timed out twice"
557
+ )
558
+ last_resp = resp
559
+ except Exception as restart_error:
560
+ print(f"Failed to restart Chrome: {restart_error}")
561
+ traceback.print_exc()
562
+ # Don't spin if restart keeps failing
563
+ time.sleep(30)
564
+ else:
565
+ # Non-timeout 2nd failure: just retry once more without restarting
566
+ last_resp = resp
567
+ else:
568
+ # We are at or beyond max attempts for this document: give up
569
+ print(
570
+ f"Document {document_id} failed {attempt_no} times, "
571
+ "marking as error and skipping further attempts"
572
+ )
573
+ try:
574
+ execute_supplied_statement(
575
+ self.db_interface,
576
+ QUERY__mark_rendered_document_completed_with_error,
577
+ {KEY__document_id: resp[KEY__document_id]}
578
+ )
579
+ except Exception as db_ex:
580
+ print(f"Failed to mark document {document_id} as error: {db_ex}")
581
+ traceback.print_exc()
582
+
583
+ doc_failures.pop(document_id, None)
584
+ last_resp = None
585
+
586
+ # Back-off sleep to avoid hammering
587
+ time.sleep(min(consecutive_failures * 2, 10))
466
588
 
467
589
  def start_chrome(self):
468
590
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaaql-middleware-python
3
- Version: 4.33.19
3
+ Version: 4.33.20
4
4
  Summary: The jaaql package, allowing for rapid development and deployment of RESTful HTTP applications
5
5
  Home-page: https://github.com/JAAQL/JAAQL-middleware-python
6
6
  Author: Software Quality Measurement and Improvement bv