qalita 2.3.0__py3-none-any.whl → 2.3.1__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.
qalita/commands/agent.py CHANGED
@@ -465,6 +465,9 @@ def run(
465
465
  # check routines before checking jobs
466
466
  check_routines(config, agent_start_datetime)
467
467
 
468
+ # try to claim one pending unassigned job compatible with this agent
469
+ claim_unassigned_jobs(config)
470
+
468
471
  check_job = send_api_request(
469
472
  request=f'/api/v1/agents/{agent_conf["context"]["remote"]["id"]}/jobs/next',
470
473
  mode="get",
@@ -1066,7 +1069,6 @@ def create_scheduled_job(routine, agent_conf):
1066
1069
  request=f"/api/v2/jobs/create",
1067
1070
  mode="post",
1068
1071
  data={
1069
- "agent_id": agent_conf["context"]["remote"]["id"],
1070
1072
  "source_id": routine["source"]["id"],
1071
1073
  "target_id": routine["target"]["id"] if routine.get("target") else None,
1072
1074
  "pack_id": routine["pack"]["id"],
@@ -1238,3 +1240,89 @@ def check_routines(config, agent_start_datetime):
1238
1240
  ROUTINE_LAST_SCHEDULED_UTC[routine.get("id")] = datetime.now(timezone.utc)
1239
1241
  else:
1240
1242
  logger.warning("Can't fetch routines or jobs from the platform")
1243
+
1244
+
1245
+ def claim_unassigned_jobs(config):
1246
+ """Claim one pending unassigned job that this agent can execute.
1247
+
1248
+ Strategy:
1249
+ - Fetch all jobs (v2)
1250
+ - Select first job with status == pending and no agent assigned
1251
+ - Check local capability: source (and target if provided) must exist locally
1252
+ - PUT to assign agent_id to this agent; on success, run job
1253
+ - On contention (another agent already claimed), ignore and return
1254
+ """
1255
+ try:
1256
+ source_conf = config.load_source_config(verbose=False)
1257
+ agent_conf = config.load_agent_config()
1258
+ local_source_ids = [s["id"] for s in source_conf["sources"] if "id" in s]
1259
+
1260
+ r = send_api_request(
1261
+ request=f"/api/v2/jobs",
1262
+ mode="get",
1263
+ )
1264
+ if r.status_code not in [200, 204]:
1265
+ return
1266
+ jobs = r.json()
1267
+ if not isinstance(jobs, list):
1268
+ return
1269
+
1270
+ def extract_id(maybe_obj, fallback_key):
1271
+ if isinstance(maybe_obj, dict):
1272
+ return maybe_obj.get("id")
1273
+ return maybe_obj if isinstance(maybe_obj, int) else None
1274
+
1275
+ for job in jobs:
1276
+ try:
1277
+ if job.get("status") != "pending":
1278
+ continue
1279
+ # unassigned: support either agent_id field or nested agent object
1280
+ is_unassigned = (
1281
+ (job.get("agent_id") is None or job.get("agent_id") == 0)
1282
+ and not job.get("agent")
1283
+ )
1284
+ if not is_unassigned:
1285
+ continue
1286
+
1287
+ src_id = extract_id(job.get("source"), "source_id") or job.get("source_id")
1288
+ if src_id not in local_source_ids:
1289
+ continue
1290
+ tgt_id = extract_id(job.get("target"), "target_id") or job.get("target_id")
1291
+ if tgt_id is not None and tgt_id not in local_source_ids:
1292
+ continue
1293
+
1294
+ # attempt to claim
1295
+ claim_resp = send_api_request(
1296
+ request=f"/api/v2/jobs/{job['id']}",
1297
+ mode="put",
1298
+ data={
1299
+ "agent_id": agent_conf["context"]["remote"]["id"],
1300
+ },
1301
+ )
1302
+ if claim_resp.status_code != 200:
1303
+ # likely contention: someone else claimed
1304
+ continue
1305
+
1306
+ # extract version ids if present
1307
+ src_ver_id = extract_id(job.get("source_version"), "source_version_id") or job.get("source_version_id")
1308
+ tgt_ver_id = extract_id(job.get("target_version"), "target_version_id") or job.get("target_version_id")
1309
+ pack_id = extract_id(job.get("pack"), "pack_id") or job.get("pack_id")
1310
+ pack_ver_id = extract_id(job.get("pack_version"), "pack_version_id") or job.get("pack_version_id")
1311
+
1312
+ logger.info(f"Claimed unassigned job {job['id']} for source {src_id} pack {pack_id}")
1313
+ job_run(
1314
+ src_id,
1315
+ src_ver_id,
1316
+ tgt_id,
1317
+ tgt_ver_id,
1318
+ pack_id,
1319
+ pack_ver_id,
1320
+ job=job,
1321
+ )
1322
+ # claim and run only one per loop
1323
+ return
1324
+ except Exception as _:
1325
+ # Be resilient; don't break the worker loop on any unexpected job shape
1326
+ continue
1327
+ except Exception as _:
1328
+ return
qalita/commands/pack.py CHANGED
@@ -586,20 +586,37 @@ def publish_new_pack_version(api_url, pack_id, pack_version, registry_id, pack_n
586
586
  source_dir=source_dir,
587
587
  config=config,
588
588
  )
589
- # Best-effort: also update compatibility metadata if provided in properties.yaml
589
+ # Best-effort: also update metadata (avatar, description, url, visibility, config, readme)
590
+ # and compatibility if provided in properties.yaml so the new version push also refreshes pack details.
590
591
  try:
591
592
  pack_dir = source_dir or f"./{pack_name}_pack"
592
593
  props = load_pack_properties(pack_dir) or {}
594
+ pack_icon = load_base64_encoded_image(os.path.join(pack_dir, "icon.png"))
595
+ readme = load_base64_encoded_text(os.path.join(pack_dir, "README.md"))
596
+ pack_config = load_json_config(os.path.join(pack_dir, "pack_conf.json"))
597
+ pack_type = props.get("type", "")
598
+ pack_description = props.get("description", "")
599
+ pack_url = props.get("url", "")
600
+ pack_visibility = props.get("visibility", "private")
593
601
  compat = props.get("compatible_sources")
594
- if isinstance(compat, builtins.list) and all(isinstance(x, str) for x in compat):
595
- _request_with_optional_config(
596
- config,
597
- api_url,
598
- f"/api/v2/packs/{pack_id}",
599
- "put",
600
- data={"compatible_sources": compat},
601
- )
602
+
603
+ # Update full metadata
604
+ update_pack_metadata(
605
+ api_url,
606
+ pack_id,
607
+ pack_icon,
608
+ pack_config,
609
+ pack_type,
610
+ pack_description,
611
+ pack_url,
612
+ pack_version,
613
+ pack_visibility,
614
+ readme,
615
+ compatible_sources=compat,
616
+ config=config,
617
+ )
602
618
  except Exception:
619
+ # Non-fatal if metadata refresh fails; continue to publish version
603
620
  pass
604
621
  publish_response = _request_with_optional_config(
605
622
  config,
@@ -689,6 +706,84 @@ def handle_version_matching(existing_versions, new_version):
689
706
  return None
690
707
 
691
708
 
709
+ def _push_with_context(agent_conf, pack_properties, pack_directory, config):
710
+ """Shared push routine used by CLI and Web UI.
711
+
712
+ Expects absolute or relative `pack_directory` and a loaded `pack_properties` dict.
713
+ """
714
+ # Extract pack details
715
+ pack_name = pack_properties.get("name")
716
+ pack_version = pack_properties.get("version")
717
+ pack_description = pack_properties.get("description", "")
718
+ pack_url = pack_properties.get("url", "")
719
+ pack_type = pack_properties.get("type", "")
720
+ pack_visibility = pack_properties.get("visibility", "private")
721
+
722
+ # Handle pack icon and README/config
723
+ pack_icon = load_base64_encoded_image(os.path.join(pack_directory, "icon.png"))
724
+ readme = load_base64_encoded_text(os.path.join(pack_directory, "README.md"))
725
+ pack_config = load_json_config(os.path.join(pack_directory, "pack_conf.json"))
726
+
727
+ # Check existing packs and versions
728
+ pack_id, existing_versions = find_existing_pack(
729
+ agent_conf["context"]["local"]["url"], pack_name, config=config
730
+ )
731
+
732
+ if pack_id:
733
+ new_version_entry = handle_version_matching(existing_versions, pack_version)
734
+ if not new_version_entry:
735
+ # Try to load compatible_sources from properties.yaml (optional)
736
+ compat = (
737
+ pack_properties.get("compatible_sources")
738
+ if isinstance(pack_properties, dict)
739
+ else None
740
+ )
741
+ update_pack_metadata(
742
+ agent_conf["context"]["local"]["url"],
743
+ pack_id,
744
+ pack_icon,
745
+ pack_config,
746
+ pack_type,
747
+ pack_description,
748
+ pack_url,
749
+ pack_version,
750
+ pack_visibility,
751
+ readme,
752
+ compatible_sources=compat,
753
+ config=config,
754
+ )
755
+ logger.success(
756
+ "Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml"
757
+ )
758
+ else:
759
+ publish_new_pack_version(
760
+ agent_conf["context"]["local"]["url"],
761
+ pack_id,
762
+ pack_version,
763
+ agent_conf["registries"][0]["id"],
764
+ pack_name,
765
+ source_dir=pack_directory,
766
+ config=config,
767
+ )
768
+ else:
769
+ create_new_pack(
770
+ agent_conf["context"]["local"]["url"],
771
+ agent_conf["registries"][0]["id"],
772
+ pack_name,
773
+ pack_icon,
774
+ pack_config,
775
+ pack_type,
776
+ pack_description,
777
+ pack_url,
778
+ pack_version,
779
+ pack_visibility,
780
+ readme,
781
+ source_dir=pack_directory,
782
+ config=config,
783
+ )
784
+ logger.success("New pack created successfully!")
785
+
786
+
692
787
  @pack.command()
693
788
  @click.option("-n", "--name", help="Name of the package", envvar="QALITA_PACK_NAME")
694
789
  @pass_config
@@ -725,78 +820,14 @@ def push(config, name):
725
820
  logger.error("Failed to load agent configuration.")
726
821
  return
727
822
 
728
- # Extract pack details
729
- pack_name = pack_properties.get("name")
730
- pack_version = pack_properties.get("version")
731
- pack_description = pack_properties.get("description", "")
732
- pack_url = pack_properties.get("url", "")
733
- pack_type = pack_properties.get("type", "")
734
- pack_visibility = pack_properties.get("visibility", "private")
735
-
736
- # Handle pack icon
737
- pack_icon = load_base64_encoded_image(f"{pack_directory}/icon.png")
738
-
739
- # Handle README file
740
- readme = load_base64_encoded_text(f"{pack_directory}/README.md")
741
-
742
- # Load pack config
743
- pack_config = load_json_config(f"{pack_directory}/pack_conf.json")
744
-
745
- # Check existing packs and versions
746
- pack_id, existing_versions = find_existing_pack(
747
- agent_conf["context"]["local"]["url"], pack_name, config=config
823
+ # Shared push routine
824
+ _push_with_context(
825
+ agent_conf,
826
+ pack_properties,
827
+ os.path.abspath(pack_directory),
828
+ config,
748
829
  )
749
830
 
750
- if pack_id:
751
- # Handle version matching and decide whether to add a new asset or update metadata
752
- new_version_entry = handle_version_matching(existing_versions, pack_version)
753
-
754
- if not new_version_entry:
755
- # Try to load compatible_sources from properties.yaml (optional)
756
- compat = pack_properties.get("compatible_sources") if isinstance(pack_properties, dict) else None
757
- update_pack_metadata(
758
- agent_conf["context"]["local"]["url"],
759
- pack_id,
760
- pack_icon,
761
- pack_config,
762
- pack_type,
763
- pack_description,
764
- pack_url,
765
- pack_version,
766
- pack_visibility,
767
- readme,
768
- compatible_sources=compat,
769
- config=config,
770
- )
771
- logger.success("Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml")
772
- else:
773
- publish_new_pack_version(
774
- agent_conf["context"]["local"]["url"],
775
- pack_id,
776
- pack_version,
777
- agent_conf["registries"][0]["id"],
778
- pack_name,
779
- source_dir=os.path.abspath(pack_directory),
780
- config=config,
781
- )
782
- else:
783
- create_new_pack(
784
- agent_conf["context"]["local"]["url"],
785
- agent_conf["registries"][0]["id"],
786
- pack_name,
787
- pack_icon,
788
- pack_config,
789
- pack_type,
790
- pack_description,
791
- pack_url,
792
- pack_version,
793
- pack_visibility,
794
- readme,
795
- source_dir=os.path.abspath(pack_directory),
796
- config=config,
797
- )
798
- logger.success("New pack created successfully!")
799
-
800
831
  logger.info("Pack pushed successfully.")
801
832
 
802
833
  except Exception as e:
@@ -842,72 +873,14 @@ def push_from_directory(config, pack_directory):
842
873
  logger.error(msg)
843
874
  return False, msg
844
875
 
845
- # Extract pack details
846
- pack_name = pack_properties.get("name")
847
- pack_version = pack_properties.get("version")
848
- pack_description = pack_properties.get("description", "")
849
- pack_url = pack_properties.get("url", "")
850
- pack_type = pack_properties.get("type", "")
851
- pack_visibility = pack_properties.get("visibility", "private")
852
-
853
- # Handle pack icon and README
854
- pack_icon = load_base64_encoded_image(os.path.join(pack_directory, "icon.png"))
855
- readme = load_base64_encoded_text(os.path.join(pack_directory, "README.md"))
856
- pack_config = load_json_config(os.path.join(pack_directory, "pack_conf.json"))
857
-
858
- # Check existing packs and versions
859
- pack_id, existing_versions = find_existing_pack(
860
- agent_conf["context"]["local"]["url"], pack_name, config=config
876
+ # Shared push routine
877
+ _push_with_context(
878
+ agent_conf,
879
+ pack_properties,
880
+ pack_directory,
881
+ config,
861
882
  )
862
883
 
863
- if pack_id:
864
- new_version_entry = handle_version_matching(existing_versions, pack_version)
865
- if not new_version_entry:
866
- # Try to load compatible_sources from properties.yaml (optional)
867
- compat = pack_properties.get("compatible_sources") if isinstance(pack_properties, dict) else None
868
- update_pack_metadata(
869
- agent_conf["context"]["local"]["url"],
870
- pack_id,
871
- pack_icon,
872
- pack_config,
873
- pack_type,
874
- pack_description,
875
- pack_url,
876
- pack_version,
877
- pack_visibility,
878
- readme,
879
- compatible_sources=compat,
880
- config=config,
881
- )
882
- logger.success("Pack metadata updated successfully ! \nIf you wanted to upload a new pack version, you must change the pack [version] attribute in properties.yaml")
883
- else:
884
- publish_new_pack_version(
885
- agent_conf["context"]["local"]["url"],
886
- pack_id,
887
- pack_version,
888
- agent_conf["registries"][0]["id"],
889
- pack_name,
890
- source_dir=pack_directory,
891
- config=config,
892
- )
893
- else:
894
- create_new_pack(
895
- agent_conf["context"]["local"]["url"],
896
- agent_conf["registries"][0]["id"],
897
- pack_name,
898
- pack_icon,
899
- pack_config,
900
- pack_type,
901
- pack_description,
902
- pack_url,
903
- pack_version,
904
- pack_visibility,
905
- readme,
906
- source_dir=pack_directory,
907
- config=config,
908
- )
909
- logger.success("New pack created successfully!")
910
-
911
884
  logger.info("Pack pushed successfully.")
912
885
  return True, "Pack pushed successfully."
913
886
  except Exception as e:
qalita/internal/utils.py CHANGED
@@ -14,7 +14,7 @@ logger = init_logging()
14
14
 
15
15
 
16
16
  def get_version():
17
- return "2.3.0"
17
+ return "2.3.1"
18
18
 
19
19
 
20
20
  def make_tarfile(output_filename, source_dir):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qalita
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: QALITA Platform Command Line Interface
5
5
  License-File: LICENSE
6
6
  Author: QALITA SAS
@@ -1,15 +1,15 @@
1
1
  qalita/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  qalita/__main__.py,sha256=mUdL2kKMsr47UP2GIJwBNcDR4yZv-ApcSH-tfcRL03g,2518
3
3
  qalita/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- qalita/commands/agent.py,sha256=HhgJA3ni6D49xxMMoCJthLYFVR5yYCPEr8GI41Vvfnw,47113
5
- qalita/commands/pack.py,sha256=xnH-uAnaqMnyVnO7KZ2DNqmd9AgF_j5AgjDE1OnG6d0,39029
4
+ qalita/commands/agent.py,sha256=t4SzLnAOsthAeNU2DCmWEkOexKQb2QQ1xhEzazyMdeM,50660
5
+ qalita/commands/pack.py,sha256=Eu1h-JJEtUBqr6OKPnwE8EuJOQcHpJtZBvKe0XFQaDg,37224
6
6
  qalita/commands/source.py,sha256=ObRD2CcDynGbMrmj__FhWvPvR0H6HEn5QSLNR8awQUQ,30945
7
7
  qalita/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  qalita/internal/config.py,sha256=P9UU3BH-CIH68-qZjmnk8juzZZ5HJ701hrY6EvrPFOA,4081
9
9
  qalita/internal/error_patterns.py,sha256=QDFNZEoTmqVOHkiGFljstY6VOhux7d4p8FIL3pKzwtQ,1447
10
10
  qalita/internal/logger.py,sha256=4xWy2BZII89nUw4tKrLXFPOlVGONm8KUBgzX2XZ3u7g,1868
11
11
  qalita/internal/request.py,sha256=s-rE9kKRhV9IEveclB2YjAY8FRevKb6qjblM8-PbV20,6209
12
- qalita/internal/utils.py,sha256=5Q9P4skA6KKkd4OvIrVH6VPniNud05L-Zr0zt395Nc4,5746
12
+ qalita/internal/utils.py,sha256=UIXH_BS2ZiP0crARISYHuJMnvajaTSHchpXoP_sZXXs,5746
13
13
  qalita/web/__init__.py,sha256=NRcQjZSizkqN0a-LutsN7X9LBQ8MhyazXBSuqzUEmXE,62
14
14
  qalita/web/app.py,sha256=92M2K7vxDIYaI81D-zvdj4VfCsaTTDs6KJZu6dy5s6U,1323
15
15
  qalita/web/blueprints/agents.py,sha256=q_SqdSzzHMsstSzEjHGtq75jFt3wQ6p3atjOSzKgiUw,17367
@@ -94,8 +94,8 @@ qalita/web/templates/studio/context-panel.html,sha256=M8yfYcJQ_2WSwnOLlCTQETzctz
94
94
  qalita/web/templates/studio/index.html,sha256=NI7zF-4cHQ3id-a_R3GNQ8gQ2Bw4A6liyEZ53AO0QOU,2823
95
95
  qalita/web/templates/studio/navbar.html,sha256=IQZ7TFOXv3r5yZmP9f1v7BMUCQU8mOajMV82UPeY3cg,746
96
96
  qalita/web/templates/studio/view-panel.html,sha256=JREfFIabpuAZctAzbE_mT_ZDk65MBWevjRNxQUrO8Pw,25198
97
- qalita-2.3.0.dist-info/METADATA,sha256=KFd1LHzaTXeRvTkC2GgwBUvn8ZWfkaXyJMQtwl_hbrE,2412
98
- qalita-2.3.0.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
99
- qalita-2.3.0.dist-info/entry_points.txt,sha256=RxEByZDKtsRrsDauuINx9XMVP6k26B_CglUmtH206Qo,47
100
- qalita-2.3.0.dist-info/licenses/LICENSE,sha256=cZt92dnxw87-VK4HB6KnmYV7mpf4JUdBkAHzFn1kQxM,22458
101
- qalita-2.3.0.dist-info/RECORD,,
97
+ qalita-2.3.1.dist-info/METADATA,sha256=q5Rw3NHpPohDXswrcEnGPTXDc5-GwrtbCMvlOtx1_EY,2412
98
+ qalita-2.3.1.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
99
+ qalita-2.3.1.dist-info/entry_points.txt,sha256=RxEByZDKtsRrsDauuINx9XMVP6k26B_CglUmtH206Qo,47
100
+ qalita-2.3.1.dist-info/licenses/LICENSE,sha256=cZt92dnxw87-VK4HB6KnmYV7mpf4JUdBkAHzFn1kQxM,22458
101
+ qalita-2.3.1.dist-info/RECORD,,
File without changes