opentf-toolkit-nightly 0.62.0.dev1286__py3-none-any.whl → 0.62.0.dev1295__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.
@@ -181,7 +181,10 @@ def _get_contextparameter_spec(app: Flask, name: str) -> Optional[Dict[str, Any]
181
181
  Initialize cache if needed, ignoring context parameters specs from
182
182
  other services.
183
183
 
184
- Adds the `watchdog_polling_delay_seconds` spec.
184
+ Adds the following specs if not already present:
185
+
186
+ - `watchdog_polling_delay_seconds`
187
+ - `availability_check_delay_seconds`
185
188
  """
186
189
  if PARAMETERS_KEY not in app.config:
187
190
  app.config[PARAMETERS_KEY] = []
@@ -191,14 +194,28 @@ def _get_contextparameter_spec(app: Flask, name: str) -> Optional[Dict[str, Any]
191
194
  app.config[PARAMETERS_KEY] += manifest.get('spec', {}).get(
192
195
  'contextParameters', []
193
196
  )
194
- app.config[PARAMETERS_KEY] += [
195
- {
196
- 'name': 'watchdog_polling_delay_seconds',
197
- 'descriptiveName': 'polling delay in seconds',
198
- 'default': 30,
199
- 'type': 'int',
200
- }
201
- ]
197
+ known = {spec['name'] for spec in app.config[PARAMETERS_KEY]}
198
+ if 'watchdog_polling_delay_seconds' not in known:
199
+ app.config[PARAMETERS_KEY].append(
200
+ {
201
+ 'name': 'watchdog_polling_delay_seconds',
202
+ 'descriptiveName': 'files watchdog polling delay in seconds',
203
+ 'default': 30,
204
+ 'type': 'int',
205
+ }
206
+ )
207
+ if 'availability_check_delay_seconds' not in known:
208
+ app.config[PARAMETERS_KEY].append(
209
+ {
210
+ 'name': 'availability_check_delay_seconds',
211
+ 'deprecatedNames': ['availability_check_delay'],
212
+ 'descriptiveName': 'availability check frequency in seconds',
213
+ 'type': 'int',
214
+ 'default': 10,
215
+ 'minValue': 10,
216
+ }
217
+ )
218
+
202
219
  app.logger.info('Configuration:')
203
220
  parameters = app.config[PARAMETERS_KEY]
204
221
  try:
@@ -465,8 +482,9 @@ def get_context_parameter(
465
482
  - `{app.name.upper()}_{name.upper()}` environment variable
466
483
  - `{name.upper()}` environment variable (if spec is shared)
467
484
  - `name` in configuration context
468
- - `spec['default']` if spec defines a default value
485
+ - for each deprecated name, in order, repeat the three steps above
469
486
  - `default` if not None
487
+ - `spec['default']` if spec defines a default value
470
488
 
471
489
  # Required parameters
472
490
 
@@ -482,9 +500,29 @@ def get_context_parameter(
482
500
 
483
501
  An integer if the parameter has a specification and is expected to
484
502
  be of type int. The actual parameter type otherwise.
503
+
504
+ # Spec format
505
+
506
+ (Optional, in `spec.contextParameters` in the service descriptor.)
507
+
508
+ ```yaml
509
+ - name: parameter_name
510
+ deprecatedNames: [alternative_parameter_names]
511
+ descriptiveName: parameter description
512
+ shared: true
513
+ type: int
514
+ default: 66
515
+ minValue: 10
516
+ maxValue: 100
517
+ ```
518
+
519
+ If `minValue` and/or `maxValue` are defined, the parameter must be
520
+ within the range.
521
+
522
+ If `type` is defined, the parameter must be of the specified type.
485
523
  """
486
524
 
487
- def _maybe_validate(v) -> int:
525
+ def _maybe_validate(v):
488
526
  newv, reason = validator(name, v) if validator else (v, None)
489
527
  lhs = f' {spec["descriptiveName"]} ({name}):' if spec else f' {name}:'
490
528
  if newv != v:
@@ -493,36 +531,46 @@ def get_context_parameter(
493
531
  app.logger.info(f'{lhs} {newv}')
494
532
  return newv
495
533
 
496
- def _fatal(*msg) -> NoReturn:
497
- app.logger.error(*msg)
534
+ def _fatal(msg: str) -> NoReturn:
535
+ app.logger.error(msg)
498
536
  sys.exit(2)
499
537
 
500
538
  spec = _get_contextparameter_spec(app, name)
501
- val = os.environ.get(name.upper()) if spec and spec.get('shared') else None
502
- val = os.environ.get(f'{app.name.upper()}_{name.upper()}', val)
503
- if val is None and name not in app.config['CONTEXT']:
504
- if spec and 'default' in spec:
505
- return _maybe_validate(spec['default'])
506
- if default is not None:
507
- return _maybe_validate(default)
539
+ shared = spec and spec.get('shared')
540
+ deprecateds: List[str] = spec.get('deprecatedNames', []) if spec else []
541
+
542
+ for alternative in [name] + deprecateds:
543
+ val = os.environ.get(alternative.upper()) if shared else None
544
+ val = os.environ.get(f'{app.name.upper()}_{alternative.upper()}', val)
545
+ val = val if val is not None else app.config['CONTEXT'].get(alternative)
546
+ if val is not None:
547
+ if alternative != name:
548
+ app.logger.warning(
549
+ f' "{alternative}" is deprecated. Consider using "{name}" instead.'
550
+ )
551
+ break
552
+ else:
553
+ val = default
554
+
555
+ if val is None and spec:
556
+ val = spec.get('default')
557
+ if val is None:
508
558
  _fatal(
509
- 'Context parameter %s not in current context and no default value specified.',
510
- name,
559
+ f'Context parameter "{name}" not in current context and no default value specified.'
511
560
  )
512
561
 
513
- val = val if val is not None else app.config['CONTEXT'][name]
514
- if not spec:
515
- return _maybe_validate(val)
516
- if spec.get('type') == 'int':
517
- try:
518
- val = int(val)
519
- except ValueError as err:
520
- _fatal('Context parameter %s not an integer: %s.', name, str(err))
521
- desc = spec['descriptiveName'][0].upper() + spec['descriptiveName'][1:]
522
- if 'minValue' in spec and val < spec['minValue']:
523
- _fatal(f'{desc} must be greater than {spec["minValue"]-1}.')
524
- if 'maxValue' in spec and val > spec['maxValue']:
525
- _fatal(f'{desc} must be less that {spec["maxValue"]+1}.')
562
+ if spec:
563
+ if spec.get('type') == 'int':
564
+ try:
565
+ val = int(val)
566
+ except ValueError as err:
567
+ _fatal(f'Context parameter "{name}" not an integer: {err}.')
568
+ desc = spec['descriptiveName'][0].upper() + spec['descriptiveName'][1:]
569
+ if 'minValue' in spec and val < spec['minValue']:
570
+ _fatal(f'{desc} must be greater than {spec["minValue"]-1}.')
571
+ if 'maxValue' in spec and val > spec['maxValue']:
572
+ _fatal(f'{desc} must be less that {spec["maxValue"]+1}.')
573
+
526
574
  return _maybe_validate(val)
527
575
 
528
576
 
@@ -23,7 +23,7 @@ import sys
23
23
  from collections import defaultdict
24
24
  from time import sleep
25
25
 
26
- from flask import request, g
26
+ from flask import Flask, request, g
27
27
 
28
28
  import yaml
29
29
 
@@ -58,6 +58,7 @@ DISPATCHQUEUE_KEY = '__dispatch queue__'
58
58
 
59
59
  WATCHDOG_POLLING_DELAY_SECONDS = 30
60
60
  WATCHDOG_POLLING_DELAY_KEY = 'watchdog_polling_delay_seconds'
61
+ AVAILABILITY_CHECK_DELAY_SECONDS = 'availability_check_delay_seconds'
61
62
 
62
63
  Handler = Callable[[Dict[str, Any]], Any]
63
64
 
@@ -121,7 +122,7 @@ def _maybe_get_item(cache: Dict[Any, Any], labels: Dict[str, str]) -> Optional[A
121
122
 
122
123
 
123
124
  def _ensure_inputs_match(
124
- plugin, labels: Dict[str, str], inputs: Dict[str, Any]
125
+ plugin: Flask, labels: Dict[str, str], inputs: Dict[str, Any]
125
126
  ) -> None:
126
127
  """Check inputs.
127
128
 
@@ -207,7 +208,7 @@ INVALID_HOOKS_DEFINITION_TEMPLATE = {
207
208
  }
208
209
 
209
210
 
210
- def _maybe_add_hook_watcher(plugin, schema: str) -> None:
211
+ def _maybe_add_hook_watcher(plugin: Flask, schema: str) -> None:
211
212
  if plugin.config['CONTEXT'][KIND_KEY] == EXECUTIONCOMMAND:
212
213
  type_ = 'CHANNEL'
213
214
  else:
@@ -235,7 +236,7 @@ def _maybe_add_hook_watcher(plugin, schema: str) -> None:
235
236
 
236
237
 
237
238
  def _read_hooks_definition(
238
- plugin, hooksfile: str, schema: str, invalid: Dict[str, Any]
239
+ plugin: Flask, hooksfile: str, schema: str, invalid: Dict[str, Any]
239
240
  ) -> None:
240
241
  """Read hooks definition file.
241
242
 
@@ -285,7 +286,9 @@ def _read_hooks_definition(
285
286
  # Dispatchers
286
287
 
287
288
 
288
- def _dispatch_providercommand(plugin, handler: Handler, body: Dict[str, Any]) -> None:
289
+ def _dispatch_providercommand(
290
+ plugin: Flask, handler: Handler, body: Dict[str, Any]
291
+ ) -> None:
289
292
  """Provider plugin dispatcher.
290
293
 
291
294
  `handler` is expected to return either a list of steps or raise a
@@ -308,7 +311,7 @@ def _dispatch_providercommand(plugin, handler: Handler, body: Dict[str, Any]) ->
308
311
  core.publish_error(f'Unexpected execution error: {err}.')
309
312
 
310
313
 
311
- def _dispatch_executioncommand(_, handler: Handler, body: Dict[str, Any]):
314
+ def _dispatch_executioncommand(_, handler: Handler, body: Dict[str, Any]) -> None:
312
315
  """Channel plugin dispatcher."""
313
316
  try:
314
317
  handler(body)
@@ -316,7 +319,9 @@ def _dispatch_executioncommand(_, handler: Handler, body: Dict[str, Any]):
316
319
  core.publish_error(f'Unexpected execution error: {err}.')
317
320
 
318
321
 
319
- def _dispatch_generatorcommand(plugin, handler: Handler, body: Dict[str, Any]):
322
+ def _dispatch_generatorcommand(
323
+ plugin: Flask, handler: Handler, body: Dict[str, Any]
324
+ ) -> None:
320
325
  """Generator plugin dispatcher."""
321
326
  try:
322
327
  labels = body['metadata'].get('labels', {})
@@ -339,7 +344,7 @@ def _dispatch_generatorcommand(plugin, handler: Handler, body: Dict[str, Any]):
339
344
  # Watchdog
340
345
 
341
346
 
342
- def _run_handlers(plugin, file, handlers):
347
+ def _run_handlers(plugin: Flask, file, handlers) -> None:
343
348
  """Run file handlers."""
344
349
  for handler, args, kwargs in handlers:
345
350
  try:
@@ -350,7 +355,7 @@ def _run_handlers(plugin, file, handlers):
350
355
  )
351
356
 
352
357
 
353
- def _watchdog(plugin, polling_delay):
358
+ def _watchdog(plugin: Flask, polling_delay: int) -> None:
354
359
  """Watch changes and call handlers when appropriate."""
355
360
  files_stat = defaultdict(float)
356
361
  files_handlers = plugin.config[WATCHEDFILES_KEY]
@@ -373,7 +378,7 @@ def _watchdog(plugin, polling_delay):
373
378
  plugin.config[WATCHEDFILES_EVENT_KEY].clear()
374
379
 
375
380
 
376
- def _start_watchdog(plugin) -> None:
381
+ def _start_watchdog(plugin: Flask) -> None:
377
382
  """Set up a watchdog that monitors specified files for changes."""
378
383
  polling_delay = max(
379
384
  WATCHDOG_POLLING_DELAY_SECONDS,
@@ -386,7 +391,7 @@ def _start_watchdog(plugin) -> None:
386
391
  ).start()
387
392
 
388
393
 
389
- def watch_file(plugin, path: str, handler, *args, **kwargs) -> None:
394
+ def watch_file(plugin: Flask, path: str, handler, *args, **kwargs) -> None:
390
395
  """Watch file changes.
391
396
 
392
397
  There can be more than one handler watching a given file. A handler
@@ -394,6 +399,10 @@ def watch_file(plugin, path: str, handler, *args, **kwargs) -> None:
394
399
  a file path (a string). It may take additional parameters. It will
395
400
  be called whenever the file changes.
396
401
 
402
+ The watchdog polls every 30 seconds by default. This can be
403
+ adjusted by setting the `watchdog_polling_delay_seconds` context
404
+ parameter (but it cannot be more frequent).
405
+
397
406
  # Required parameters
398
407
 
399
408
  - plugin: a Flask application
@@ -420,12 +429,18 @@ def watch_file(plugin, path: str, handler, *args, **kwargs) -> None:
420
429
  plugin.config[WATCHEDFILES_EVENT_KEY].set()
421
430
 
422
431
 
423
- def _watchnotifier(plugin, polling_delay, check, items, notify):
432
+ def _watchnotifier(
433
+ plugin: Flask,
434
+ polling_delay: int,
435
+ check: Callable[..., bool],
436
+ items,
437
+ notify: Callable[[], None],
438
+ ):
424
439
  reference = {}
425
440
  while True:
426
441
  sleep(polling_delay)
427
442
  try:
428
- statuses = {item: check(item) for item in items.copy()}
443
+ statuses = {item: check(item) for item in list(items)}
429
444
  if statuses != reference:
430
445
  notify()
431
446
  reference = statuses
@@ -435,7 +450,9 @@ def _watchnotifier(plugin, polling_delay, check, items, notify):
435
450
  )
436
451
 
437
452
 
438
- def watch_and_notify(plugin, status, items, notify):
453
+ def watch_and_notify(
454
+ plugin: Flask, status: Callable[..., Any], items, notify: Callable[[], None]
455
+ ) -> None:
439
456
  """Watch statuses changes in items.
440
457
 
441
458
  Check item status change at regular interval, call notify if
@@ -444,11 +461,11 @@ def watch_and_notify(plugin, status, items, notify):
444
461
  # Required parameters
445
462
 
446
463
  - plugin: a Flask application
447
- - status: a function taking an item and returning a boolean
464
+ - status: a function taking an item and returning a value
448
465
  - items: an iterable
449
466
  - notify: a function of no arguments
450
467
  """
451
- polling_delay = get_context_parameter(plugin, 'availability_check_delay')
468
+ polling_delay = get_context_parameter(plugin, AVAILABILITY_CHECK_DELAY_SECONDS)
452
469
 
453
470
  plugin.logger.debug('Starting watch notifier thread.')
454
471
  threading.Thread(
@@ -459,7 +476,7 @@ def watch_and_notify(plugin, status, items, notify):
459
476
 
460
477
 
461
478
  def _subscribe(
462
- plugin,
479
+ plugin: Flask,
463
480
  cat_prefix: Optional[str],
464
481
  cat: Optional[str],
465
482
  cat_version: Optional[str],
@@ -499,7 +516,7 @@ def _subscribe(
499
516
  return subscribe(kind=kind, target='inbox', app=plugin, labels=labels)
500
517
 
501
518
 
502
- def run_plugin(plugin):
519
+ def run_plugin(plugin: Flask) -> None:
503
520
  """Start and run plugin.
504
521
 
505
522
  Subscribe to the relevant events before startup and tries to
@@ -553,7 +570,7 @@ def make_plugin(
553
570
  schema=None,
554
571
  configfile=None,
555
572
  args: Optional[Any] = None,
556
- ):
573
+ ) -> Flask:
557
574
  """Create and return a new plugin service.
558
575
 
559
576
  One and only one of `channel`, `generator`, `provider`, `providers`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: opentf-toolkit-nightly
3
- Version: 0.62.0.dev1286
3
+ Version: 0.62.0.dev1295
4
4
  Summary: OpenTestFactory Orchestrator Toolkit
5
5
  Home-page: https://gitlab.com/henixdevelopment/open-source/opentestfactory/python-toolkit
6
6
  Author: Martin Lafaix
@@ -1,4 +1,4 @@
1
- opentf/commons/__init__.py,sha256=thcqhAp--rYDw-JIBIkgpaLu1kEk3pyPJz8brx7f7vU,22599
1
+ opentf/commons/__init__.py,sha256=2hd1g14g9pnZl1uip6Sh90YSmLDWzkH1SGeZweRJ4z8,24180
2
2
  opentf/commons/auth.py,sha256=gXRp_0Tf3bfd65F4QiQmh6C6vR9y3ugag_0DSvozJFk,15898
3
3
  opentf/commons/config.py,sha256=RVSSdQhMle4oCo_z_AR2EQ4U6sUjSxw-qVBtjKuJVfo,10219
4
4
  opentf/commons/exceptions.py,sha256=7dhUXO8iyAbqVwlUKxZhgRzGqVcb7LkG39hFlm-VxIA,2407
@@ -55,11 +55,11 @@ opentf/schemas/opentestfactory.org/v1beta1/Workflow.json,sha256=QZ8mM9PhzsI9gTmw
55
55
  opentf/schemas/opentestfactory.org/v1beta2/ServiceConfig.json,sha256=rEvK2YWL5lG94_qYgR_GnLWNsaQhaQ-2kuZdWJr5NnY,3517
56
56
  opentf/scripts/launch_java_service.sh,sha256=S0jAaCuv2sZy0Gf2NGBuPX-eD531rcM-b0fNyhmzSjw,2423
57
57
  opentf/scripts/startup.py,sha256=vOGxl7xBcp1-_LsAKiOmeOqFl2iT81A2XRrXBLUrNi4,22785
58
- opentf/toolkit/__init__.py,sha256=mYeJPZ92ulbTBItqEsZgF4nnuRh6G19QPY3Jxc92ifc,23028
58
+ opentf/toolkit/__init__.py,sha256=ohrde5mcMY26p64E0Z2XunZAWYOiEkXKTg5E1J4TGGc,23571
59
59
  opentf/toolkit/channels.py,sha256=whLfPVT5PksVlprmoeb2ktaZ3KEhqyryUCVWBJq7PeY,24308
60
60
  opentf/toolkit/core.py,sha256=fqnGgaYnuVcd4fyeNIwpc0QtyUo7jsKeVgdkBfY3iqo,9443
61
- opentf_toolkit_nightly-0.62.0.dev1286.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
- opentf_toolkit_nightly-0.62.0.dev1286.dist-info/METADATA,sha256=_LVp3XnOgkbwM-JnwBA7EiqniPxVpA6bfZGfhdBZW_o,2192
63
- opentf_toolkit_nightly-0.62.0.dev1286.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
64
- opentf_toolkit_nightly-0.62.0.dev1286.dist-info/top_level.txt,sha256=_gPuE6GTT6UNXy1DjtmQSfCcZb_qYA2vWmjg7a30AGk,7
65
- opentf_toolkit_nightly-0.62.0.dev1286.dist-info/RECORD,,
61
+ opentf_toolkit_nightly-0.62.0.dev1295.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
+ opentf_toolkit_nightly-0.62.0.dev1295.dist-info/METADATA,sha256=Dftf4ExDEznTennMkNJ7wCZoK1HONVmPhb_jwMqi6W4,2192
63
+ opentf_toolkit_nightly-0.62.0.dev1295.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
64
+ opentf_toolkit_nightly-0.62.0.dev1295.dist-info/top_level.txt,sha256=_gPuE6GTT6UNXy1DjtmQSfCcZb_qYA2vWmjg7a30AGk,7
65
+ opentf_toolkit_nightly-0.62.0.dev1295.dist-info/RECORD,,