arkindex-base-worker 0.5.1b1__py3-none-any.whl → 0.5.1b2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkindex-base-worker
3
- Version: 0.5.1b1
3
+ Version: 0.5.1b2
4
4
  Summary: Base Worker to easily build Arkindex ML workflows
5
5
  Author-email: Teklia <contact@teklia.com>
6
6
  Maintainer-email: Teklia <contact@teklia.com>
@@ -1,11 +1,11 @@
1
- arkindex_base_worker-0.5.1b1.dist-info/licenses/LICENSE,sha256=NVshRi1efwVezMfW7xXYLrdDr2Li1AfwfGOd5WuH1kQ,1063
1
+ arkindex_base_worker-0.5.1b2.dist-info/licenses/LICENSE,sha256=NVshRi1efwVezMfW7xXYLrdDr2Li1AfwfGOd5WuH1kQ,1063
2
2
  arkindex_worker/__init__.py,sha256=Sdt5KXn8EgURb2MurYVrUWaHbH3iFA1XLRo0Lc5AJ44,250
3
3
  arkindex_worker/cache.py,sha256=x1d1oVF297ItLoZnPkZQoEefa39ZigrwRoHC_6az94k,10731
4
4
  arkindex_worker/image.py,sha256=GvIpW7LNSalVw3Obt9nySDWnW7-NbC0__SWREEQqVCk,20696
5
5
  arkindex_worker/models.py,sha256=bPQzGZNs5a6z6DEcygsa8T33VOqPlMUbwKzHqlKzwbw,9923
6
6
  arkindex_worker/utils.py,sha256=MbbJT8oh8DMHHR-vidFeXdUH0TSXGWm7ZDGWzrRXoEY,9933
7
7
  arkindex_worker/worker/__init__.py,sha256=Ho4rzGDtpFwd13cl5tL45yAVZJuyGCOIOroLPGZsvmk,18399
8
- arkindex_worker/worker/base.py,sha256=03WLu7R8vca54LI00g-S0EqIbFiNaIsgZjN6zmfHisw,20126
8
+ arkindex_worker/worker/base.py,sha256=fbRJ5vDON3DfSQfwxFqto85HY8Dw2_YgOmnm5cxbQ2g,21725
9
9
  arkindex_worker/worker/classification.py,sha256=qvykymkgd4nGywHCxL8obo4egstoGsmWNS4Ztc1qNWQ,11024
10
10
  arkindex_worker/worker/corpus.py,sha256=MeIMod7jkWyX0frtD0a37rhumnMV3p9ZOC1xwAoXrAA,2291
11
11
  arkindex_worker/worker/dataset.py,sha256=tVaPx43vaH-KTtx4w5V06e26ha8XPfiJTRzBXlu928Y,5273
@@ -21,15 +21,16 @@ examples/standalone/python/worker.py,sha256=Zr4s4pHvgexEjlkixLFYZp1UuwMLeoTxjyNG
21
21
  examples/tooled/python/worker.py,sha256=kIYlHLsO5UpwX4XtERRq4tf2qTsvqKK30C-w8t0yyhA,1821
22
22
  hooks/pre_gen_project.py,sha256=xQJERv3vv9VzIqcBHI281eeWLWREXUF4mMw7PvJHHXM,269
23
23
  tests/__init__.py,sha256=DG--S6IpGl399rzSAjDdHL76CkOIeZIjajCcyUSDhOQ,241
24
- tests/conftest.py,sha256=NwYfoWO5ZTRfK5REYLGExt64CS1OPTcTpJ4LfbvuouE,22048
25
- tests/test_base_worker.py,sha256=dA00oxauTSCwnFX3ZFBl-RI71HN6GmK48FBBW_oYN-k,30627
24
+ tests/conftest.py,sha256=jRFykK7cQm2AsZduVp78oEvKtHyzm3wr95IVkF4XpyY,23825
25
+ tests/test_base_worker.py,sha256=3YjhjxSWVjEWFYS8m8pYYoaVAhHFkJLNTs0QPQIkBDM,32651
26
26
  tests/test_cache.py,sha256=nnEFfAAqtYHk2ymOwN0spXJd8nrRiwp3voj0tOmIbQ8,10407
27
- tests/test_dataset_worker.py,sha256=z8ydliUlwW2j-irgLAotJMacgJXkVvF5TgsWLyCn1Jo,22087
27
+ tests/test_dataset_worker.py,sha256=iDJM2C4PfQNH0r4_QqSWoPt8BcM0geUUdODtWY0Z9PA,22412
28
28
  tests/test_element.py,sha256=2G9M15TLxQRmvrWM9Kw2ucnElh4kSv_oF_5FYwwAxTY,13181
29
29
  tests/test_image.py,sha256=NEIp5evr6QoTWgJ-_fze19IEFm_hG6YEcuW1kxnxS_I,28013
30
30
  tests/test_merge.py,sha256=REpZ13jkq_qm_4L5URQgFy5lxvPZtXxQEiWfYLMdmF0,7956
31
+ tests/test_modern_config.py,sha256=Bm-a4LYQXgLZWQX7AmVyfJW0LNoLy1wj2d2GjzDkcBk,2683
31
32
  tests/test_utils.py,sha256=nYL1s2ViZoLoMiNpLGDaWwxf8dJ1D8aT522AO-PVaEQ,3607
32
- tests/test_elements_worker/__init__.py,sha256=Fh4nkbbyJSMv_VtjQxnWrOqTnxXaaWI8S9WU0VrzCHs,179
33
+ tests/test_elements_worker/__init__.py,sha256=2t3NciCIOun_N-Wv63FWGsTm5W9N3mbwAWVuFORlMg8,308
33
34
  tests/test_elements_worker/test_classification.py,sha256=nya7veSPR_O9G41Enodp2-o6AifMBcaSTWJP2vXSSJ4,30133
34
35
  tests/test_elements_worker/test_cli.py,sha256=a23i1pUDbXi23MUtbWwGEcLLrmc_YlrbDgOG3h66wLM,2620
35
36
  tests/test_elements_worker/test_corpus.py,sha256=kscJyM8k1njYJJFGuvliVzn89lWh41mEyDCCawnp3W8,5483
@@ -54,7 +55,7 @@ worker-demo/tests/conftest.py,sha256=XzNMNeg6pmABUAH8jN6eZTlZSFGLYjS3-DTXjiRN6Yc
54
55
  worker-demo/tests/test_worker.py,sha256=3DLd4NRK4bfyatG5P_PK4k9P9tJHx9XQq5_ryFEEFVg,304
55
56
  worker-demo/worker_demo/__init__.py,sha256=2BPomV8ZMNf3YXJgloatKeHQCE6QOkwmsHGkO6MkQuM,125
56
57
  worker-demo/worker_demo/worker.py,sha256=Rt-DjWa5iBP08k58NDZMfeyPuFbtNcbX6nc5jFX7GNo,440
57
- arkindex_base_worker-0.5.1b1.dist-info/METADATA,sha256=LZx_PDuHE_v64WNubUcSPXN2FCWBwnHxEIxogiprVtU,3137
58
- arkindex_base_worker-0.5.1b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
59
- arkindex_base_worker-0.5.1b1.dist-info/top_level.txt,sha256=-vNjP2VfROx0j83mdi9aIqRZ88eoJjxeWz-R_gPgyXU,49
60
- arkindex_base_worker-0.5.1b1.dist-info/RECORD,,
58
+ arkindex_base_worker-0.5.1b2.dist-info/METADATA,sha256=oAv0lAu9iGzHWuW8YL_i7uf1dqppslQ7iRznUJYLiGw,3137
59
+ arkindex_base_worker-0.5.1b2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
+ arkindex_base_worker-0.5.1b2.dist-info/top_level.txt,sha256=-vNjP2VfROx0j83mdi9aIqRZ88eoJjxeWz-R_gPgyXU,49
61
+ arkindex_base_worker-0.5.1b2.dist-info/RECORD,,
@@ -9,12 +9,13 @@ import os
9
9
  import shutil
10
10
  from pathlib import Path
11
11
  from tempfile import mkdtemp
12
+ from typing import Any
12
13
 
13
14
  import gnupg
14
15
  import yaml
15
16
 
16
17
  from arkindex import options_from_env
17
- from arkindex.exceptions import ErrorResponse
18
+ from arkindex.exceptions import ClientError, ErrorResponse
18
19
  from arkindex_worker import logger
19
20
  from arkindex_worker.cache import (
20
21
  check_version,
@@ -260,6 +261,48 @@ class BaseWorker:
260
261
 
261
262
  logger.info(f"Loaded {worker_run['summary']} from API")
262
263
 
264
+ def _process_config_item(item: dict) -> tuple[str, Any]:
265
+ if not item["secret"]:
266
+ return (item["key"], item["value"])
267
+
268
+ # Load secret, only available in Arkindex EE
269
+ try:
270
+ secret = self.load_secret(Path(item["value"]))
271
+ except ClientError as e:
272
+ logger.error(
273
+ f"Failed to retrieve the secret {item['value']}, probably an Arkindex Community Edition: {e}"
274
+ )
275
+ return (item["key"], None)
276
+
277
+ return (item["key"], secret)
278
+
279
+ # Load worker run information
280
+ try:
281
+ config = self.api_client.request(
282
+ "RetrieveWorkerRunConfiguration", id=self.worker_run_id
283
+ )
284
+
285
+ # Provide the same configuration through all previous attributes
286
+ self.config = self.user_configuration = dict(
287
+ map(_process_config_item, config["configuration"])
288
+ )
289
+
290
+ # Provide secret values through the previous attribute
291
+ self.secrets = {
292
+ item["key"]: self.config[item["key"]]
293
+ for item in config["configuration"]
294
+ if item["secret"]
295
+ }
296
+ logger.info("Using modern configuration")
297
+
298
+ return # Stop here once we have modern configuration
299
+
300
+ except ErrorResponse as e:
301
+ if e.status_code != 400:
302
+ raise
303
+ logger.info("Modern configuration is not available")
304
+
305
+ # Use old-style configuration with local merge
263
306
  # Load model version configuration when available
264
307
  model_version = worker_run.get("model_version")
265
308
  if model_version:
tests/conftest.py CHANGED
@@ -165,6 +165,13 @@ def _mock_worker_run_api(responses):
165
165
  content_type="application/json",
166
166
  )
167
167
 
168
+ # By default, stick to classic configuration
169
+ responses.add(
170
+ responses.GET,
171
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
172
+ status=400,
173
+ )
174
+
168
175
 
169
176
  @pytest.fixture
170
177
  def _mock_worker_run_no_revision_api(responses):
@@ -233,6 +240,56 @@ def _mock_worker_run_no_revision_api(responses):
233
240
  )
234
241
 
235
242
 
243
+ @pytest.fixture
244
+ def mock_base_worker_modern_conf(mocker, responses):
245
+ """
246
+ Provide a base worker to test modern configuration with (not provided in the fixture)
247
+ """
248
+ worker = BaseWorker()
249
+ mocker.patch.object(sys, "argv")
250
+ worker.args = worker.parser.parse_args()
251
+
252
+ payload = {
253
+ "id": "56785678-5678-5678-5678-567856785678",
254
+ "parents": [],
255
+ "worker_version": {
256
+ "id": "12341234-1234-1234-1234-123412341234",
257
+ "worker": {
258
+ "id": "deadbeef-1234-5678-1234-worker",
259
+ "name": "Fake worker",
260
+ "slug": "fake_worker",
261
+ "type": "classifier",
262
+ },
263
+ "revision": {"hash": "deadbeef1234"},
264
+ "configuration": {
265
+ "configuration": {"extra_key1": "not showing up"},
266
+ "user_configuration": {"extra_key2": "not showing up"},
267
+ },
268
+ },
269
+ "configuration": {
270
+ "id": "af0daaf4-983e-4703-a7ed-a10f146d6684",
271
+ "name": "my-userconfig",
272
+ "configuration": {
273
+ "extra_key3": "not showing up",
274
+ },
275
+ },
276
+ "model_version": None,
277
+ "process": {
278
+ "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeffff",
279
+ "corpus": CORPUS_ID,
280
+ },
281
+ "summary": "Worker Fake worker @ 123412",
282
+ }
283
+ responses.add(
284
+ responses.GET,
285
+ "http://testserver/api/v1/process/workers/56785678-5678-5678-5678-567856785678/",
286
+ status=200,
287
+ json=payload,
288
+ )
289
+
290
+ return worker
291
+
292
+
236
293
  @pytest.fixture
237
294
  def _mock_activity_calls(responses):
238
295
  """
tests/test_base_worker.py CHANGED
@@ -190,6 +190,14 @@ def test_configure_worker_run(mocker, responses, caplog):
190
190
  body=json.dumps(payload),
191
191
  content_type="application/json",
192
192
  )
193
+
194
+ # By default, stick to classic configuration
195
+ responses.add(
196
+ responses.GET,
197
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
198
+ status=400,
199
+ )
200
+
193
201
  worker.args = worker.parser.parse_args()
194
202
  assert worker.is_read_only is False
195
203
  assert worker.worker_run_id == "56785678-5678-5678-5678-567856785678"
@@ -205,6 +213,11 @@ def test_configure_worker_run(mocker, responses, caplog):
205
213
  logging.INFO,
206
214
  "Loaded Worker Fake worker @ 123412 from API",
207
215
  ),
216
+ (
217
+ "arkindex_worker",
218
+ logging.INFO,
219
+ "Modern configuration is not available",
220
+ ),
208
221
  ("arkindex_worker", logging.INFO, "Loaded user configuration from WorkerRun"),
209
222
  ("arkindex_worker", logging.INFO, "User configuration retrieved"),
210
223
  ]
@@ -213,9 +226,16 @@ def test_configure_worker_run(mocker, responses, caplog):
213
226
 
214
227
 
215
228
  @pytest.mark.usefixtures("_mock_worker_run_no_revision_api")
216
- def test_configure_worker_run_no_revision(mocker, caplog):
229
+ def test_configure_worker_run_no_revision(mocker, caplog, responses):
217
230
  worker = BaseWorker()
218
231
 
232
+ # By default, stick to classic configuration
233
+ responses.add(
234
+ responses.GET,
235
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
236
+ status=400,
237
+ )
238
+
219
239
  mocker.patch.object(sys, "argv", ["worker"])
220
240
  worker.args = worker.parser.parse_args()
221
241
  assert worker.is_read_only is False
@@ -227,7 +247,12 @@ def test_configure_worker_run_no_revision(mocker, caplog):
227
247
  worker.configure()
228
248
 
229
249
  assert caplog.record_tuples == [
230
- ("arkindex_worker", logging.INFO, "Loaded Worker Fake worker @ 1 from API")
250
+ ("arkindex_worker", logging.INFO, "Loaded Worker Fake worker @ 1 from API"),
251
+ (
252
+ "arkindex_worker",
253
+ logging.INFO,
254
+ "Modern configuration is not available",
255
+ ),
231
256
  ]
232
257
 
233
258
 
@@ -283,6 +308,13 @@ def test_configure_user_configuration_defaults(mocker, responses):
283
308
  content_type="application/json",
284
309
  )
285
310
 
311
+ # By default, stick to classic configuration
312
+ responses.add(
313
+ responses.GET,
314
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
315
+ status=400,
316
+ )
317
+
286
318
  worker.configure()
287
319
 
288
320
  assert worker.user_configuration == {
@@ -340,6 +372,13 @@ def test_configure_user_config_debug(mocker, responses, debug):
340
372
  body=json.dumps(payload),
341
373
  content_type="application/json",
342
374
  )
375
+
376
+ # By default, stick to classic configuration
377
+ responses.add(
378
+ responses.GET,
379
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
380
+ status=400,
381
+ )
343
382
  worker.args = worker.parser.parse_args()
344
383
  worker.configure()
345
384
 
@@ -388,6 +427,12 @@ def test_configure_worker_run_missing_conf(mocker, responses):
388
427
  body=json.dumps(payload),
389
428
  content_type="application/json",
390
429
  )
430
+ # By default, stick to classic configuration
431
+ responses.add(
432
+ responses.GET,
433
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
434
+ status=400,
435
+ )
391
436
  worker.args = worker.parser.parse_args()
392
437
  worker.configure()
393
438
 
@@ -430,6 +475,12 @@ def test_configure_worker_run_no_worker_run_conf(mocker, responses):
430
475
  body=json.dumps(payload),
431
476
  content_type="application/json",
432
477
  )
478
+ # By default, stick to classic configuration
479
+ responses.add(
480
+ responses.GET,
481
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
482
+ status=400,
483
+ )
433
484
  worker.args = worker.parser.parse_args()
434
485
  worker.configure()
435
486
 
@@ -480,6 +531,12 @@ def test_configure_load_model_configuration(mocker, responses):
480
531
  body=json.dumps(payload),
481
532
  content_type="application/json",
482
533
  )
534
+ # By default, stick to classic configuration
535
+ responses.add(
536
+ responses.GET,
537
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
538
+ status=400,
539
+ )
483
540
  worker.args = worker.parser.parse_args()
484
541
  assert worker.is_read_only is False
485
542
  assert worker.worker_run_id == "56785678-5678-5678-5678-567856785678"
@@ -947,6 +1004,13 @@ def test_worker_config_multiple_source(
947
1004
  content_type="application/json",
948
1005
  )
949
1006
 
1007
+ # By default, stick to classic configuration
1008
+ responses.add(
1009
+ responses.GET,
1010
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
1011
+ status=400,
1012
+ )
1013
+
950
1014
  # Create and configure a worker
951
1015
  monkeypatch.setattr(sys, "argv", ["worker"])
952
1016
  worker = BaseWorker()
@@ -430,6 +430,7 @@ def test_run_no_sets(mocker, caplog, mock_dataset_worker):
430
430
 
431
431
  assert [(level, message) for _, level, message in caplog.record_tuples] == [
432
432
  (logging.INFO, "Loaded Worker Fake worker @ 123412 from API"),
433
+ (logging.INFO, "Modern configuration is not available"),
433
434
  (logging.WARNING, "No sets to process, stopping."),
434
435
  ]
435
436
 
@@ -453,6 +454,7 @@ def test_run_initial_dataset_state_error(
453
454
 
454
455
  assert [(level, message) for _, level, message in caplog.record_tuples] == [
455
456
  (logging.INFO, "Loaded Worker Fake worker @ 123412 from API"),
457
+ (logging.INFO, "Modern configuration is not available"),
456
458
  (
457
459
  logging.WARNING,
458
460
  "Failed running worker on Set (train) from Dataset (dataset_id): AssertionError('When processing a set, its dataset state should be Complete.')",
@@ -497,6 +499,7 @@ def test_run_download_dataset_artifact_api_error(
497
499
 
498
500
  assert [(level, message) for _, level, message in caplog.record_tuples] == [
499
501
  (logging.INFO, "Loaded Worker Fake worker @ 123412 from API"),
502
+ (logging.INFO, "Modern configuration is not available"),
500
503
  (
501
504
  logging.INFO,
502
505
  "Retrieving data for Set (train) from Dataset (dataset_id) (1/1)",
@@ -550,6 +553,7 @@ def test_run_no_downloaded_dataset_artifact_error(
550
553
 
551
554
  assert [(level, message) for _, level, message in caplog.record_tuples] == [
552
555
  (logging.INFO, "Loaded Worker Fake worker @ 123412 from API"),
556
+ (logging.INFO, "Modern configuration is not available"),
553
557
  (
554
558
  logging.INFO,
555
559
  "Retrieving data for Set (train) from Dataset (dataset_id) (1/1)",
@@ -626,6 +630,7 @@ def test_run(
626
630
 
627
631
  assert [(level, message) for _, level, message in caplog.record_tuples] == [
628
632
  (logging.INFO, "Loaded Worker Fake worker @ 123412 from API"),
633
+ (logging.INFO, "Modern configuration is not available"),
629
634
  (
630
635
  logging.INFO,
631
636
  "Retrieving data for Set (train) from Dataset (dataset_id) (1/1)",
@@ -4,4 +4,8 @@ BASE_API_CALLS = [
4
4
  "GET",
5
5
  "http://testserver/api/v1/process/workers/56785678-5678-5678-5678-567856785678/",
6
6
  ),
7
+ (
8
+ "GET",
9
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
10
+ ),
7
11
  ]
@@ -0,0 +1,81 @@
1
+ def test_simple_configuration(mock_base_worker_modern_conf, responses):
2
+ # Provide the full configuration directly from the worker run
3
+ responses.add(
4
+ responses.GET,
5
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
6
+ status=200,
7
+ json={"configuration": [{"key": "some_key", "value": "test", "secret": False}]},
8
+ )
9
+
10
+ mock_base_worker_modern_conf.configure()
11
+
12
+ assert mock_base_worker_modern_conf.config == {"some_key": "test"}
13
+ assert (
14
+ mock_base_worker_modern_conf.user_configuration
15
+ == mock_base_worker_modern_conf.config
16
+ )
17
+ assert mock_base_worker_modern_conf.secrets == {}
18
+
19
+
20
+ def test_empty(mock_base_worker_modern_conf, responses):
21
+ # Provide the full configuration directly from the worker run
22
+ responses.add(
23
+ responses.GET,
24
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
25
+ status=200,
26
+ json={"configuration": []},
27
+ )
28
+
29
+ mock_base_worker_modern_conf.configure()
30
+
31
+ assert mock_base_worker_modern_conf.config == {}
32
+ assert (
33
+ mock_base_worker_modern_conf.user_configuration
34
+ == mock_base_worker_modern_conf.config
35
+ )
36
+ assert mock_base_worker_modern_conf.secrets == {}
37
+
38
+
39
+ def test_with_secrets(mock_base_worker_modern_conf, responses):
40
+ # Provide the full configuration directly from the worker run
41
+ responses.add(
42
+ responses.GET,
43
+ "http://testserver/api/v1/workers/runs/56785678-5678-5678-5678-567856785678/configuration/",
44
+ status=200,
45
+ json={
46
+ "configuration": [
47
+ {"key": "some_key", "value": "test", "secret": False},
48
+ {
49
+ "key": "a_secret",
50
+ "value": "471b9e64-29af-48dc-8bda-1a64a2da0c12",
51
+ "secret": True,
52
+ },
53
+ ]
54
+ },
55
+ )
56
+
57
+ # Provide a secret value
58
+ responses.add(
59
+ responses.GET,
60
+ "http://testserver/api/v1/secret/471b9e64-29af-48dc-8bda-1a64a2da0c12",
61
+ status=200,
62
+ json={
63
+ "id": "471b9e64-29af-48dc-8bda-1a64a2da0c12",
64
+ "name": "a_secret",
65
+ "content": "My super duper secret value",
66
+ },
67
+ )
68
+
69
+ mock_base_worker_modern_conf.configure()
70
+
71
+ assert mock_base_worker_modern_conf.config == {
72
+ "a_secret": "My super duper secret value",
73
+ "some_key": "test",
74
+ }
75
+ assert (
76
+ mock_base_worker_modern_conf.user_configuration
77
+ == mock_base_worker_modern_conf.config
78
+ )
79
+ assert mock_base_worker_modern_conf.secrets == {
80
+ "a_secret": "My super duper secret value"
81
+ }