kodexa 7.0.1a8003211616__py3-none-any.whl → 7.0.1a9194120328__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.
kodexa/platform/client.py CHANGED
@@ -85,7 +85,7 @@ from kodexa.model.objects import (
85
85
  ReprocessRequest,
86
86
  PageExtensionPack,
87
87
  PageOrganization,
88
- DocumentFamilyStatistics, MessageContext, PagePrompt, Prompt, GuidanceSet, PageGuidanceSet,
88
+ DocumentFamilyStatistics, MessageContext, PagePrompt, Prompt, GuidanceSet, PageGuidanceSet, DocumentEmbedding,
89
89
  )
90
90
 
91
91
  logger = logging.getLogger()
@@ -491,9 +491,9 @@ class ComponentEndpoint(ClientEndpoint, OrganizationOwned):
491
491
  # Yield each endpoint in the current page
492
492
  for endpoint in (
493
493
  self.get_page_class(list_response.json())
494
- .model_validate(list_response.json())
495
- .set_client(self.client)
496
- .to_endpoints()
494
+ .model_validate(list_response.json())
495
+ .set_client(self.client)
496
+ .to_endpoints()
497
497
  ):
498
498
  yield endpoint
499
499
 
@@ -548,7 +548,7 @@ class ComponentEndpoint(ClientEndpoint, OrganizationOwned):
548
548
  Returns:
549
549
  The created component.
550
550
  """
551
- url = f"/api/{self.get_type()}/{self.organization.slug}/"
551
+ url = f"/api/{self.get_type()}/{self.organization.slug}"
552
552
  get_response = self.client.post(
553
553
  url, component.model_dump(mode="json", by_alias=True)
554
554
  )
@@ -774,7 +774,7 @@ class EntitiesEndpoint:
774
774
  Returns:
775
775
  PageProject: The page of projects belonging to the organization.
776
776
  """
777
- url = f"/api/{self.get_type()}/"
777
+ url = f"/api/{self.get_type()}"
778
778
  get_response = self.client.get(
779
779
  url, params={"filter": f"organization.id: '{organization.id}'"}
780
780
  )
@@ -1598,6 +1598,83 @@ class OrganizationEndpoint(Organization, EntityEndpoint):
1598
1598
  """
1599
1599
  return TaxonomiesEndpoint().set_client(self.client).set_organization(self)
1600
1600
 
1601
+ @property
1602
+ def available_templates(self, page=1, page_size=10, query="*"):
1603
+ """
1604
+ Get the available templates for the organization.
1605
+
1606
+ Returns:
1607
+ MarketplaceEndpoint: The marketplace endpoint of the organization.
1608
+ """
1609
+ url = f"/api/organizations/{self.id}/availableTemplates"
1610
+ response = self.client.get(url, params={"page": page, "pageSize": page_size, "query": query})
1611
+ return PageProjectTemplateEndpoint.model_validate(response.json()).set_client(self.client)
1612
+
1613
+ @property
1614
+ def available_models(self, page=1, page_size=10, query="*"):
1615
+ """
1616
+ Get the available models for the organization.
1617
+
1618
+ Returns:
1619
+ MarketplaceEndpoint: The marketplace endpoint of the organization.
1620
+ """
1621
+ url = f"/api/organizations/{self.id}/availableModels"
1622
+ response = self.client.get(url, params={"page": page, "pageSize": page_size, "query": query})
1623
+ return PageStoreEndpoint.model_validate(response.json()).set_client(self.client)
1624
+
1625
+ @property
1626
+ def available_assistants(self, page=1, page_size=10, query="*"):
1627
+ """
1628
+ Get the available assistants for the organization.
1629
+
1630
+ Returns:
1631
+ MarketplaceEndpoint: The marketplace endpoint of the organization.
1632
+ """
1633
+ url = f"/api/organizations/{self.id}/availableAssistants"
1634
+ response = self.client.get(url, params={"page": page, "pageSize": page_size, "query": query})
1635
+ return PageAssistantDefinitionEndpoint.model_validate(response.json()).set_client(self.client)
1636
+
1637
+ def get_subscriptions(self, page: int = 1, page_size: int = 10) -> "PageProductSubscriptionEndpoint":
1638
+ """
1639
+ Get the subscriptions of the organization.
1640
+
1641
+ Returns:
1642
+ The subscriptions of the organization.
1643
+ """
1644
+ url = f"/api/productSubscriptions"
1645
+ params = {
1646
+ "filter": f"organization.id: '{self.id}'",
1647
+ "page": page,
1648
+ "pageSize": page_size
1649
+ }
1650
+ response = self.client.get(url, params=params)
1651
+
1652
+ from kodexa.model.entities.product_subscription import PageProductSubscriptionEndpoint
1653
+ return PageProductSubscriptionEndpoint.model_validate(response.json()).set_client(self.client)
1654
+
1655
+ def remove_subscription(self, subscription: "ProductSubscription") -> None:
1656
+ """
1657
+ Remove a subscription from the organization.
1658
+
1659
+ Args:
1660
+ subscription_id (str): The id of the subscription to remove.
1661
+ """
1662
+ url = f"/api/productSubscriptions/{subscription.id}"
1663
+ self.client.delete(url)
1664
+
1665
+ def add_subscription(self, product: "Product") -> None:
1666
+ """
1667
+ Add a subscription to the organization.
1668
+
1669
+ Args:
1670
+ product (Product): The product to subscribe to.
1671
+ """
1672
+ url = f"/api/productSubscriptions"
1673
+ from kodexa.model.entities.product_subscription import ProductSubscription
1674
+ new_product_subscription = ProductSubscription(organization=self.detach(), product=product)
1675
+ print(new_product_subscription.model_dump_json(by_alias=True))
1676
+ self.client.post(url, body=json.loads(new_product_subscription.model_dump_json(by_alias=True)))
1677
+
1601
1678
 
1602
1679
  class ComponentsEndpoint(ClientEndpoint):
1603
1680
  """
@@ -1725,13 +1802,15 @@ class ComponentInstanceEndpoint(ClientEndpoint, SlugBasedMetadata):
1725
1802
  raise Exception(f"Component {self.ref} already exists")
1726
1803
 
1727
1804
  if exists:
1728
- self.client.put(url, self.model_dump(mode="json", by_alias=True))
1805
+ response = self.client.put(url, self.model_dump(mode="json", by_alias=True))
1806
+ process_response(response)
1729
1807
  return self.post_deploy()
1730
1808
 
1731
- self.client.post(
1809
+ response = self.client.post(
1732
1810
  f"/api/{self.get_type()}/{self.org_slug}",
1733
1811
  self.model_dump(mode="json", by_alias=True),
1734
1812
  )
1813
+ process_response(response)
1735
1814
  return self.post_deploy()
1736
1815
 
1737
1816
 
@@ -2072,6 +2151,33 @@ class ProjectTaxonomiesEndpoint(ProjectResourceEndpoint):
2072
2151
  return TaxonomyEndpoint
2073
2152
 
2074
2153
 
2154
+ class ProjectGuidanceEndpoint(ProjectResourceEndpoint):
2155
+
2156
+ def get_type(self) -> str:
2157
+ return "guidance"
2158
+
2159
+ def get_instance_class(self, object_dict=None):
2160
+ return GuidanceSetEndpoint
2161
+
2162
+
2163
+ class ProjectDataFormEndpoint(ProjectResourceEndpoint):
2164
+
2165
+ def get_type(self) -> str:
2166
+ return "dataForms"
2167
+
2168
+ def get_instance_class(self, object_dict=None):
2169
+ return DataFormEndpoint
2170
+
2171
+
2172
+ class ProjectDashboardEndpoint(ProjectResourceEndpoint):
2173
+
2174
+ def get_type(self) -> str:
2175
+ return "dashboards"
2176
+
2177
+ def get_instance_class(self, object_dict=None):
2178
+ return DashboardEndpoint
2179
+
2180
+
2075
2181
  class ProjectStoresEndpoint(ProjectResourceEndpoint):
2076
2182
  """Represents a project stores endpoint"""
2077
2183
 
@@ -2352,20 +2458,25 @@ class ProjectEndpoint(EntityEndpoint, Project):
2352
2458
  self,
2353
2459
  stores: List["StoreEndpoint"] = None,
2354
2460
  taxonomies: List["TaxonomyEndpoint"] = None,
2355
- ) -> "ProjectEndpoint":
2461
+ data_forms: List["DataFormEndpoint"] = None,
2462
+ guidance: List["GuidanceSetEndpoint"] = None,
2463
+ dashboards: List["DashboardEndpoint"] = None,
2464
+ ):
2356
2465
  """Update the resources of the project.
2357
2466
 
2358
2467
  Args:
2359
2468
  stores (List["StoreEndpoint"], optional): List of store endpoints to update.
2360
2469
  taxonomies (List["TaxonomyEndpoint"], optional): List of taxonomy endpoints to update.
2361
-
2362
- Returns:
2363
- ProjectEndpoint: The updated project endpoint.
2470
+ data_forms (List["DataFormEndpoint"], optional): List of data form endpoints to update.
2471
+ guidance (List["GuidanceSetEndpoint"], optional): List of guidance set endpoints to update.
2472
+ dashboards (List["DashboardEndpoint"], optional): List of dashboard endpoints to update.
2364
2473
  """
2365
2474
  project_resources_update = ProjectResourcesUpdate()
2366
2475
  project_resources_update.store_refs = []
2367
2476
  project_resources_update.taxonomy_refs = []
2368
2477
  project_resources_update.dashboard_refs = []
2478
+ project_resources_update.data_form_refs = []
2479
+ project_resources_update.guidance_set_refs = []
2369
2480
 
2370
2481
  if stores:
2371
2482
  project_resources_update.store_refs = [store.ref for store in stores]
@@ -2374,6 +2485,18 @@ class ProjectEndpoint(EntityEndpoint, Project):
2374
2485
  project_resources_update.taxonomy_refs = [
2375
2486
  taxonomy.ref for taxonomy in taxonomies
2376
2487
  ]
2488
+ if data_forms:
2489
+ project_resources_update.data_form_refs = [
2490
+ data_form.ref for data_form in data_forms
2491
+ ]
2492
+ if guidance:
2493
+ project_resources_update.guidance_set_refs = [
2494
+ guidance.ref for guidance in guidance
2495
+ ]
2496
+ if dashboards:
2497
+ project_resources_update.dashboard_refs = [
2498
+ dashboard.ref for dashboard in dashboards
2499
+ ]
2377
2500
 
2378
2501
  self.client.put(
2379
2502
  f"/api/projects/{self.id}/resources",
@@ -2416,6 +2539,15 @@ class ProjectEndpoint(EntityEndpoint, Project):
2416
2539
  """
2417
2540
  return ProjectTaxonomiesEndpoint().set_client(self.client).set_project(self)
2418
2541
 
2542
+ @property
2543
+ def guidance(self) -> "ProjectGuidanceEndpoint":
2544
+ """Get the guidance sets endpoint of the project.
2545
+
2546
+ Returns:
2547
+ GuidanceSetsEndpoint: The guidance sets endpoint of the project.
2548
+ """
2549
+ return ProjectGuidanceEndpoint().set_client(self.client).set_project(self)
2550
+
2419
2551
  @property
2420
2552
  def assistants(self) -> ProjectAssistantsEndpoint:
2421
2553
  """Get the assistants endpoint of the project.
@@ -2450,6 +2582,43 @@ class ProjectEndpoint(EntityEndpoint, Project):
2450
2582
  return [ProjectTag.model_validate(tag) for tag in response.json()]
2451
2583
 
2452
2584
 
2585
+ class GuidanceSetsEndpoint(EntitiesEndpoint):
2586
+ """Represents a message endpoint"""
2587
+
2588
+ def get_type(self) -> str:
2589
+ """
2590
+ Get the type of the endpoint.
2591
+
2592
+ Returns:
2593
+ str: The type of the endpoint, in this case "guidance".
2594
+ """
2595
+ return "guidance"
2596
+
2597
+ def get_instance_class(self, object_dict=None):
2598
+ """
2599
+ Get the instance class of the endpoint.
2600
+
2601
+ Args:
2602
+ object_dict (dict, optional): An optional dictionary object. Defaults to None.
2603
+
2604
+ Returns:
2605
+ GuidanceSetEndpoint: The instance class of the endpoint.
2606
+ """
2607
+ return GuidanceSetEndpoint
2608
+
2609
+ def get_page_class(self, object_dict=None):
2610
+ """
2611
+ Get the page class of the endpoint.
2612
+
2613
+ Args:
2614
+ object_dict (dict, optional): An optional dictionary object. Defaults to None.
2615
+
2616
+ Returns:
2617
+ PageGuidanceSetEndpoint: The page class of the endpoint.
2618
+ """
2619
+ return PageGuidanceSetEndpoint
2620
+
2621
+
2453
2622
  class MessagesEndpoint(EntitiesEndpoint):
2454
2623
  """Represents a message endpoint"""
2455
2624
 
@@ -2652,12 +2821,13 @@ class ProjectsEndpoint(EntitiesEndpoint):
2652
2821
  """
2653
2822
  return PageProjectEndpoint
2654
2823
 
2655
- def find_by_name(self, project_name: str) -> Optional[ProjectEndpoint]:
2824
+ def find_by_name(self, project_name: str, organization: Optional[Organization] = None) -> Optional[ProjectEndpoint]:
2656
2825
  """
2657
2826
  Find a project by name.
2658
2827
 
2659
2828
  Args:
2660
2829
  project_name (str): The name of the project to find.
2830
+ organization (Organization, optional): The organization to search in. Defaults to None.
2661
2831
 
2662
2832
  Returns:
2663
2833
  Optional[ProjectEndpoint]: The project endpoint if found, None otherwise.
@@ -2665,8 +2835,8 @@ class ProjectsEndpoint(EntitiesEndpoint):
2665
2835
 
2666
2836
  url = f"/api/{self.get_type()}"
2667
2837
  filters = {"filter": [f"name: '{project_name}'"]}
2668
- if self.organization is not None:
2669
- filters["filter"].append(f"organization.id: '{self.organization.id}'")
2838
+ if organization is not None:
2839
+ filters["filter"].append(f"organization.id: '{organization.id}'")
2670
2840
  get_response = self.client.get(url, params=filters)
2671
2841
  if len(get_response.json()["content"]) > 0:
2672
2842
  return ProjectEndpoint.model_validate(
@@ -2734,7 +2904,7 @@ class ProjectsEndpoint(EntitiesEndpoint):
2734
2904
  if self.organization is not None:
2735
2905
  params["filter"].append(f"organization.id: '{self.organization.id}'")
2736
2906
 
2737
- get_response = self.client.get(f"/api/{self.get_type()}/", params=params)
2907
+ get_response = self.client.get(f"/api/{self.get_type()}", params=params)
2738
2908
 
2739
2909
  return PageProjectEndpoint.model_validate(get_response.json()).set_client(
2740
2910
  self.client
@@ -2837,16 +3007,17 @@ class GuidanceEndpoint(ComponentEndpoint, ClientEndpoint, OrganizationOwned):
2837
3007
  """
2838
3008
  Represents a guidance endpoint.
2839
3009
 
2840
- This class is used to interact with the prompts endpoint of the API.
3010
+ This class is used to interact with the guidance endpoint of the API.
2841
3011
  It provides methods to get the type, page class, and instance class of the endpoint,
2842
3012
  as well as to deploy an extension pack from a URL.
2843
3013
  """
3014
+
2844
3015
  def get_type(self) -> str:
2845
3016
  """
2846
3017
  Get the type of the endpoint.
2847
3018
 
2848
3019
  Returns:
2849
- str: The type of the endpoint, "prompts".
3020
+ str: The type of the endpoint, "guidance".
2850
3021
  """
2851
3022
  return "guidance"
2852
3023
 
@@ -2883,6 +3054,7 @@ class PromptsEndpoint(ComponentEndpoint, ClientEndpoint, OrganizationOwned):
2883
3054
  It provides methods to get the type, page class, and instance class of the endpoint,
2884
3055
  as well as to deploy an extension pack from a URL.
2885
3056
  """
3057
+
2886
3058
  def get_type(self) -> str:
2887
3059
  """
2888
3060
  Get the type of the endpoint.
@@ -4973,10 +5145,28 @@ class DataStoreEndpoint(StoreEndpoint):
4973
5145
 
4974
5146
 
4975
5147
  class DocumentStoreEndpoint(StoreEndpoint):
4976
- """Represents a document store that can be used to store files and their related document representations."""
4977
-
4978
5148
  """Represents a document store that can be used to store files and then their related document representations"""
4979
5149
 
5150
+ def query_by_embedding(self, embedding: list[float], threshold: float, limit: int):
5151
+ """
5152
+ Query the document store by an embedding.
5153
+
5154
+ Args:
5155
+ embedding (list[float]): The embedding to query by.
5156
+ threshold (int): The threshold to use for the query.
5157
+ limit (int): The limit of the query.
5158
+
5159
+ Returns:
5160
+ list[DocumentEmbedding]: a list of document embeddings
5161
+ """
5162
+ url = "/api/embeddings/query"
5163
+ embedding_query = {"embedding": embedding, "threshold": threshold, "limit": limit, "storeRef": self.ref}
5164
+ response = self.client.post(url, body=embedding_query)
5165
+ process_response(response)
5166
+
5167
+ # We get a list of the document embeddings
5168
+ return [DocumentEmbedding.model_validate(embedding) for embedding in response.json()]
5169
+
4980
5170
  def delete_by_path(self, object_path: str):
4981
5171
  """
4982
5172
  Delete the content stored in the store at the given path.
@@ -5271,6 +5461,8 @@ class DocumentStoreEndpoint(StoreEndpoint):
5271
5461
  count += 1
5272
5462
  if limit and count >= limit:
5273
5463
  break
5464
+ if limit and count >= limit:
5465
+ break
5274
5466
  page += 1
5275
5467
 
5276
5468
  def filter(
@@ -5357,6 +5549,7 @@ class DocumentStoreEndpoint(StoreEndpoint):
5357
5549
  f"api/stores/{self.ref.replace(':', '/')}/fs",
5358
5550
  params={"path": path, "meta": True},
5359
5551
  )
5552
+ process_response(get_response)
5360
5553
  return DocumentFamilyEndpoint.model_validate(get_response.json()).set_client(
5361
5554
  self.client
5362
5555
  )
@@ -5448,6 +5641,7 @@ class ModelStoreEndpoint(DocumentStoreEndpoint):
5448
5641
  response = self.client.get(
5449
5642
  f"/api/stores/{self.ref.replace(':', '/')}/trainings/{training_id}/content"
5450
5643
  )
5644
+ process_response(response)
5451
5645
  from zipfile import ZipFile
5452
5646
  from io import BytesIO
5453
5647
 
@@ -5467,6 +5661,7 @@ class ModelStoreEndpoint(DocumentStoreEndpoint):
5467
5661
  response = self.client.get(
5468
5662
  f"/api/stores/{self.ref.replace(':', '/')}/implementation"
5469
5663
  )
5664
+ process_response(response)
5470
5665
  from zipfile import ZipFile
5471
5666
  from io import BytesIO
5472
5667
 
@@ -5652,6 +5847,11 @@ class ModelStoreEndpoint(DocumentStoreEndpoint):
5652
5847
  zipf.write(path_hit, relative_path)
5653
5848
  num_hits += 1
5654
5849
 
5850
+ if num_hits == 0:
5851
+ print(
5852
+ f"No files found for implementation in {metadata.base_dir} with {metadata.contents}"
5853
+ )
5854
+
5655
5855
  return num_hits
5656
5856
 
5657
5857
  def upload_contents(self, metadata: ModelContentMetadata, dry_run=False):
@@ -5669,10 +5869,11 @@ class ModelStoreEndpoint(DocumentStoreEndpoint):
5669
5869
  num_hits = self.build_implementation_zip(metadata)
5670
5870
  if num_hits > 0 and not dry_run:
5671
5871
  with open("implementation.zip", "rb") as zip_content:
5672
- self.client.post(
5872
+ response = self.client.post(
5673
5873
  f"/api/stores/{self.ref.replace(':', '/')}/implementation",
5674
5874
  files={"implementation": zip_content},
5675
5875
  )
5876
+ process_response(response)
5676
5877
  results.append(f"{num_hits} files packaged and deployed to {self.ref}")
5677
5878
  if not metadata.keep_zip:
5678
5879
  Path("implementation.zip").unlink()
@@ -5754,6 +5955,8 @@ def process_response(response) -> requests.Response:
5754
5955
  raise Exception(f"Unauthorized ({response.text})")
5755
5956
  if response.status_code == 404:
5756
5957
  raise Exception(f"Not found ({response.text})")
5958
+ if response.status_code in [301, 302]:
5959
+ raise Exception(f"Redirected ({response.text})")
5757
5960
  if response.status_code == 405:
5758
5961
  raise Exception("Method not allowed")
5759
5962
  if response.status_code == 500:
@@ -5769,11 +5972,6 @@ def process_response(response) -> requests.Response:
5769
5972
 
5770
5973
  return response
5771
5974
 
5772
- #
5773
- # The Kodexa Client is the way that brings everything together
5774
- #
5775
- #
5776
-
5777
5975
 
5778
5976
  OBJECT_TYPES = {
5779
5977
  "extensionPacks": {
@@ -5969,9 +6167,10 @@ class ExtractionEngineEndpoint:
5969
6167
  """
5970
6168
  response = self.client.post(
5971
6169
  "/api/extractionEngine/extract",
5972
- data={"taxonomyJson": taxonomy.model_dump_json()},
6170
+ data={"taxonomyJson": taxonomy.model_dump_json(by_alias=True)},
5973
6171
  files={"document": document.to_kddb()},
5974
6172
  )
6173
+ print(response.json())
5975
6174
  return [
5976
6175
  DataObject.model_validate(data_object) for data_object in response.json()
5977
6176
  ]
@@ -5992,7 +6191,7 @@ class ExtractionEngineEndpoint:
5992
6191
  response = self.client.post(
5993
6192
  "/api/extractionEngine/extract",
5994
6193
  params="full",
5995
- data={"taxonomyJson": taxonomy.model_dump_json()},
6194
+ data={"taxonomyJson": taxonomy.model_dump_json(by_alias=True)},
5996
6195
  files={"document": document.to_kddb()},
5997
6196
  )
5998
6197
  return {
@@ -6023,7 +6222,7 @@ class ExtractionEngineEndpoint:
6023
6222
  response = self.client.post(
6024
6223
  "/api/extractionEngine/extract",
6025
6224
  params={"format": format},
6026
- data={"taxonomyJson": taxonomy.model_dump_json()},
6225
+ data={"taxonomyJson": taxonomy.model_dump_json(by_alias=True)},
6027
6226
  files={"document": document.to_kddb()},
6028
6227
  )
6029
6228
  return response.text
@@ -6046,7 +6245,7 @@ class KodexaClient:
6046
6245
  messages (MessagesEndpoint): An endpoint for messages.
6047
6246
  """
6048
6247
 
6049
- def __init__(self, url=None, access_token=None, profile="default"):
6248
+ def __init__(self, url=None, access_token=None, profile=None):
6050
6249
  from kodexa import KodexaPlatform
6051
6250
 
6052
6251
  self.base_url = url if url is not None else KodexaPlatform.get_url(profile)
@@ -6064,16 +6263,18 @@ class KodexaClient:
6064
6263
  self.channels = ChannelsEndpoint(self)
6065
6264
  self.assistants = AssistantsEndpoint(self)
6066
6265
  self.messages = MessagesEndpoint(self)
6266
+ from kodexa.model.entities.product import ProductsEndpoint
6267
+ self.products = ProductsEndpoint(self)
6268
+ self.guidance_sets = GuidanceSetsEndpoint(self)
6067
6269
 
6068
6270
  @staticmethod
6069
- def login(url, email, password):
6271
+ def login(url, token):
6070
6272
  """
6071
6273
  A static method to login to the Kodexa platform.
6072
6274
 
6073
6275
  Args:
6074
6276
  url (str): The URL for the Kodexa platform.
6075
- email (str): The email for the user.
6076
- password (str): The password for the user.
6277
+ token (str): The email for the user.
6077
6278
 
6078
6279
  Returns:
6079
6280
  KodexaClient: A KodexaClient instance.
@@ -6084,9 +6285,10 @@ class KodexaClient:
6084
6285
  from requests.auth import HTTPBasicAuth
6085
6286
 
6086
6287
  obj_response = requests.get(
6087
- f"{url}/api/account/me/token",
6088
- auth=HTTPBasicAuth(email, password),
6089
- headers={"content-type": "application/json"}
6288
+ f"{url}/api/account/me",
6289
+ headers={"content-type": "application/json",
6290
+ "x-access-token": token,
6291
+ "cf-access-token": os.environ.get("CF_TOKEN", "")}
6090
6292
  )
6091
6293
  if obj_response.status_code == 200:
6092
6294
  return KodexaClient(url, obj_response.text)
@@ -6218,6 +6420,7 @@ class KodexaClient:
6218
6420
  params=params,
6219
6421
  headers={
6220
6422
  "x-access-token": self.access_token,
6423
+ "cf-access-token": os.environ.get("CF_TOKEN", ""),
6221
6424
  "content-type": "application/json",
6222
6425
  },
6223
6426
  )
@@ -6244,7 +6447,9 @@ class KodexaClient:
6244
6447
  params=params,
6245
6448
  headers={
6246
6449
  "x-access-token": self.access_token,
6450
+ "cf-access-token": os.environ.get("CF_TOKEN", ""),
6247
6451
  "content-type": "application/json",
6452
+ "X-Requested-With": "XMLHttpRequest",
6248
6453
  }
6249
6454
  )
6250
6455
 
@@ -6266,7 +6471,10 @@ class KodexaClient:
6266
6471
  Returns:
6267
6472
  requests.Response: The response from the server.
6268
6473
  """
6269
- headers = {"x-access-token": self.access_token}
6474
+ headers = {
6475
+ "x-access-token": self.access_token,
6476
+ "X-Requested-With": "XMLHttpRequest",
6477
+ "cf-access-token": os.environ.get("CF_TOKEN", "")}
6270
6478
  if files is None:
6271
6479
  headers["content-type"] = "application/json"
6272
6480
 
@@ -6296,7 +6504,9 @@ class KodexaClient:
6296
6504
  Returns:
6297
6505
  requests.Response: The response from the server.
6298
6506
  """
6299
- headers = {"x-access-token": self.access_token}
6507
+ headers = {"x-access-token": self.access_token,
6508
+ "cf-access-token": os.environ.get("CF_TOKEN", ""),
6509
+ "X-Requested-With": "XMLHttpRequest"}
6300
6510
  if files is None:
6301
6511
  headers["content-type"] = "application/json"
6302
6512
 
@@ -6324,7 +6534,9 @@ class KodexaClient:
6324
6534
  response = requests.delete(
6325
6535
  self.get_url(url),
6326
6536
  params=params,
6327
- headers={"x-access-token": self.access_token}
6537
+ headers={"x-access-token": self.access_token,
6538
+ "cf-access-token": os.environ.get("CF_TOKEN", ""),
6539
+ "X-Requested-With": "XMLHttpRequest"}
6328
6540
  )
6329
6541
  return process_response(response)
6330
6542
 
@@ -6440,6 +6652,18 @@ class KodexaClient:
6440
6652
  )
6441
6653
  )
6442
6654
 
6655
+ for guidance in project.guidance.list():
6656
+ guidance_file = os.path.join(
6657
+ project_export_dir, f"guidance-{guidance.slug}-{guidance.version}.json"
6658
+ )
6659
+ with open(guidance_file, "w") as f:
6660
+ f.write(
6661
+ json.dumps(
6662
+ guidance.model_dump(mode="json", by_alias=True), indent=4
6663
+ )
6664
+ )
6665
+
6666
+
6443
6667
  def import_project(self, organization: OrganizationEndpoint, import_path: str):
6444
6668
  """
6445
6669
  A method to import a project.
@@ -6533,6 +6757,13 @@ class KodexaClient:
6533
6757
  taxonomy.ref = None
6534
6758
  taxonomies.append(organization.taxonomies.create(taxonomy))
6535
6759
 
6760
+ for guidance_file in glob.glob(os.path.join(import_path, "guidance-*.json")):
6761
+ with open(guidance_file, "r") as f:
6762
+ guidance = GuidanceSetEndpoint.model_validate(json.load(f))
6763
+ guidance.org_slug = None
6764
+ guidance.ref = None
6765
+ organization.guidance.create(guidance)
6766
+
6536
6767
  import time
6537
6768
 
6538
6769
  time.sleep(4)
@@ -6603,6 +6834,8 @@ class KodexaClient:
6603
6834
 
6604
6835
  raise Exception("A store must have a storeType")
6605
6836
 
6837
+ from kodexa.model.entities.product import ProductEndpoint
6838
+ from kodexa.model.entities.product_subscription import ProductSubscriptionEndpoint
6606
6839
  known_components = {
6607
6840
  "taxonomy": TaxonomyEndpoint,
6608
6841
  "pipeline": PipelineEndpoint,
@@ -6626,6 +6859,8 @@ class KodexaClient:
6626
6859
  "prompt": PromptEndpoint,
6627
6860
  "guidance": GuidanceSetEndpoint,
6628
6861
  "channel": ChannelEndpoint,
6862
+ "product": ProductEndpoint,
6863
+ "productSubscription": ProductSubscriptionEndpoint,
6629
6864
  }
6630
6865
 
6631
6866
  if component_type in known_components: