pyxecm 1.4__py3-none-any.whl → 1.5__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.

Potentially problematic release.


This version of pyxecm might be problematic. Click here for more details.

@@ -1,5 +1,6 @@
1
1
  """
2
2
  Salesforce Module to interact with the Salesforce API
3
+ See: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm
3
4
 
4
5
  Class: Salesforce
5
6
  Methods:
@@ -14,15 +15,29 @@ exist_result_item: Check if an dict item is in the response
14
15
  of the Salesforce API call
15
16
  get_result_value: Check if a defined value (based on a key) is in the Salesforce API response
16
17
 
17
- authenticate : Authenticates at Salesforce API
18
-
19
- get_user: Get a Salesforce user based on its ID.
20
- add_user: Add a new Salesforce user.
18
+ authenticate: Authenticates at Salesforce API
21
19
 
20
+ get_object_id_by_name: Get the ID of a given Salesforce object with a given type and name
22
21
  get_object: Get a Salesforce object based on a defined
23
22
  field value and return selected result fields.
24
23
  add_object: Add object to Salesforce. This is a generic wrapper method
25
24
  for the actual add methods.
25
+
26
+ get_group: Get a Salesforce group based on its ID.
27
+ add_group: Add a new Salesforce group.
28
+ update_group: Update a Salesforce group.
29
+ get_group_members: Get Salesforce group members
30
+ add_group_member: Add a user or group to a Salesforce group
31
+
32
+ get_all_user_profiles: Get all user profiles
33
+ get_user_profile_id: Get a user profile ID by profile name
34
+ get_user_id: Get a user ID by user name
35
+ get_user: Get a Salesforce user based on its ID.
36
+ add_user: Add a new Salesforce user.
37
+ update_user: Update a Salesforce user.
38
+ update_user_password: Update the password of a Salesforce user.
39
+ update_user_photo: update the Salesforce user photo.
40
+
26
41
  add_account: Add a new Account object to Salesforce.
27
42
  add_product: Add a new Product object to Salesforce.
28
43
  add_opportunity: Add a new Opportunity object to Salesfoce.
@@ -38,6 +53,7 @@ __credits__ = ["Kai-Philip Gatzweiler"]
38
53
  __maintainer__ = "Dr. Marc Diefenbruch"
39
54
  __email__ = "mdiefenb@opentext.com"
40
55
 
56
+ import os
41
57
  import json
42
58
  import logging
43
59
 
@@ -46,12 +62,13 @@ import requests
46
62
 
47
63
  logger = logging.getLogger("pyxecm.customizer.salesforce")
48
64
 
49
- request_login_headers = {
65
+ REQUEST_LOGIN_HEADERS = {
50
66
  "Content-Type": "application/x-www-form-urlencoded",
51
67
  "Accept": "application/json",
52
68
  }
53
69
 
54
70
  REQUEST_TIMEOUT = 60
71
+ SALESFORCE_API_VERSION = "v60.0"
55
72
 
56
73
  class Salesforce(object):
57
74
  """Used to retrieve and automate stettings in Salesforce."""
@@ -84,21 +101,56 @@ class Salesforce(object):
84
101
  security_token (str, optional): security token for Salesforce login
85
102
  """
86
103
 
104
+ # The instance URL is also returned by the authenticate call
105
+ # but typically it is identical to the base_url.
106
+ self._instance_url = base_url
107
+
87
108
  salesforce_config = {}
88
109
 
89
- # Set the authentication endpoints and credentials
90
- salesforce_config["baseUrl"] = base_url
110
+ # Store the credentials and parameters in a config dictionary:
91
111
  salesforce_config["clientId"] = client_id
92
112
  salesforce_config["clientSecret"] = client_secret
93
113
  salesforce_config["username"] = username
94
114
  salesforce_config["password"] = password
95
115
  salesforce_config["securityToken"] = security_token
116
+
117
+ # Set the Salesforce URLs and REST API endpoints:
118
+ salesforce_config["baseUrl"] = base_url
119
+ salesforce_config["objectUrl"] = salesforce_config[
120
+ "baseUrl"
121
+ ] + "/services/data/{}/sobjects/".format(SALESFORCE_API_VERSION)
122
+ salesforce_config["queryUrl"] = salesforce_config[
123
+ "baseUrl"
124
+ ] + "/services/data/{}/query/".format(SALESFORCE_API_VERSION)
125
+ salesforce_config["compositeUrl"] = salesforce_config[
126
+ "baseUrl"
127
+ ] + "/services/data/{}/composite/".format(SALESFORCE_API_VERSION)
128
+ salesforce_config["connectUrl"] = salesforce_config[
129
+ "baseUrl"
130
+ ] + "/services/data/{}/connect/".format(SALESFORCE_API_VERSION)
131
+ salesforce_config["toolingUrl"] = salesforce_config[
132
+ "baseUrl"
133
+ ] + "/services/data/{}/tooling/".format(SALESFORCE_API_VERSION)
96
134
  if authorization_url:
97
135
  salesforce_config["authenticationUrl"] = authorization_url
98
136
  else:
99
137
  salesforce_config["authenticationUrl"] = (
100
138
  salesforce_config["baseUrl"] + "/services/oauth2/token"
101
139
  )
140
+ # URLs that are based on the objectURL (sobjects/):
141
+ salesforce_config["userUrl"] = salesforce_config["objectUrl"] + "User/"
142
+ salesforce_config["groupUrl"] = salesforce_config["objectUrl"] + "Group/"
143
+ salesforce_config["groupMemberUrl"] = (
144
+ salesforce_config["objectUrl"] + "GroupMember/"
145
+ )
146
+ salesforce_config["accountUrl"] = salesforce_config["objectUrl"] + "Account/"
147
+ salesforce_config["productUrl"] = salesforce_config["objectUrl"] + "Product2/"
148
+ salesforce_config["opportunityUrl"] = (
149
+ salesforce_config["objectUrl"] + "Opportunity/"
150
+ )
151
+ salesforce_config["caseUrl"] = salesforce_config["objectUrl"] + "Case/"
152
+ salesforce_config["assetUrl"] = salesforce_config["objectUrl"] + "Asset/"
153
+ salesforce_config["contractUrl"] = salesforce_config["objectUrl"] + "Contract/"
102
154
 
103
155
  # Set the data for the token request
104
156
  salesforce_config["authenticationData"] = {
@@ -111,6 +163,8 @@ class Salesforce(object):
111
163
 
112
164
  self._config = salesforce_config
113
165
 
166
+ # end method definition
167
+
114
168
  def config(self) -> dict:
115
169
  """Returns the configuration dictionary
116
170
 
@@ -143,8 +197,10 @@ class Salesforce(object):
143
197
 
144
198
  request_header = {
145
199
  "Authorization": "Bearer {}".format(self._access_token),
146
- "Content-Type": content_type,
147
200
  }
201
+ if content_type:
202
+ request_header["Content-Type"] = content_type
203
+
148
204
  return request_header
149
205
 
150
206
  # end method definition
@@ -278,16 +334,16 @@ class Salesforce(object):
278
334
 
279
335
  # Already authenticated and session still valid?
280
336
  if self._access_token and not revalidate:
281
- logger.info(
337
+ logger.debug(
282
338
  "Session still valid - return existing access token -> %s",
283
339
  str(self._access_token),
284
340
  )
285
341
  return self._access_token
286
342
 
287
343
  request_url = self.config()["authenticationUrl"]
288
- request_header = request_login_headers
344
+ request_header = REQUEST_LOGIN_HEADERS
289
345
 
290
- logger.info("Requesting Salesforce Access Token from -> %s", request_url)
346
+ logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
291
347
 
292
348
  authenticate_post_body = self.credentials()
293
349
 
@@ -346,18 +402,17 @@ class Salesforce(object):
346
402
  """
347
403
 
348
404
  if not self._access_token or not self._instance_url:
349
- logger.error("Authentication required.")
350
- return None
405
+ self.authenticate()
351
406
 
352
407
  request_header = self.request_header()
353
- request_url = f"{self._instance_url}/services/data/v52.0/query/"
408
+ request_url = self.config()["queryUrl"]
354
409
 
355
410
  query = f"SELECT Id FROM {object_type} WHERE {name_field} = '{name}'"
356
411
 
357
412
  retries = 0
358
413
  while True:
359
414
  response = requests.get(
360
- request_url,
415
+ url=request_url,
361
416
  headers=request_header,
362
417
  params={"q": query},
363
418
  timeout=REQUEST_TIMEOUT,
@@ -367,13 +422,13 @@ class Salesforce(object):
367
422
  object_id = self.get_result_value(response, "Id")
368
423
  return object_id
369
424
  elif response.status_code == 401 and retries == 0:
370
- logger.warning("Session has expired - try to re-authenticate...")
425
+ logger.debug("Session has expired - try to re-authenticate...")
371
426
  self.authenticate(revalidate=True)
372
427
  request_header = self.request_header()
373
428
  retries += 1
374
429
  else:
375
430
  logger.error(
376
- "Failed to get Salesforce object ID for object type -> %s and object name -> %s; status -> %s; error -> %s",
431
+ "Failed to get Salesforce object ID for object type -> '%s' and object name -> '%s'; status -> %s; error -> %s",
377
432
  object_type,
378
433
  name,
379
434
  response.status_code,
@@ -383,36 +438,6 @@ class Salesforce(object):
383
438
 
384
439
  # end method definition
385
440
 
386
- def get_profile_id(self, profile_name: str) -> Optional[str]:
387
- """Get a user profile ID by profile name.
388
-
389
- Args:
390
- profile_name (str): Name of the User Profile.
391
-
392
- Returns:
393
- Optional[str]: Technical ID of the user profile.
394
- """
395
-
396
- return self.get_object_id_by_name(object_type="Profile", name=profile_name)
397
-
398
- # end method definition
399
-
400
- def get_user_id(self, username: str) -> Optional[str]:
401
- """Get a user ID by user name.
402
-
403
- Args:
404
- username (str): Name of the User.
405
-
406
- Returns:
407
- Optional[str]: Technical ID of the user
408
- """
409
-
410
- return self.get_object_id_by_name(
411
- object_type="User", name=username, name_field="Username"
412
- )
413
-
414
- # end method definition
415
-
416
441
  def get_object(
417
442
  self,
418
443
  object_type: str,
@@ -433,11 +458,33 @@ class Salesforce(object):
433
458
 
434
459
  Returns:
435
460
  dict | None: Dictionary with the Salesforce object data.
461
+
462
+ Example response:
463
+ {
464
+ 'totalSize': 2,
465
+ 'done': True,
466
+ 'records': [
467
+ {
468
+ 'attributes': {
469
+ 'type': 'Opportunity',
470
+ 'url': '/services/data/v60.0/sobjects/Opportunity/006Dn00000EclybIAB'
471
+ },
472
+ 'Id': '006Dn00000EclybIAB'
473
+ },
474
+ {
475
+ 'attributes': {
476
+ 'type': 'Opportunity',
477
+ 'url': '/services/data/v60.0/sobjects/Opportunity/006Dn00000EclyfIAB'
478
+ },
479
+ 'Id': '006Dn00000EclyfIAB'
480
+ }
481
+ ]
482
+ }
436
483
  """
437
484
 
438
485
  if not self._access_token or not self._instance_url:
439
- logger.error("Authentication required.")
440
- return None
486
+ self.authenticate()
487
+
441
488
  if search_field and not search_value:
442
489
  logger.error(
443
490
  "No search value has been provided for search field -> %s!",
@@ -445,7 +492,7 @@ class Salesforce(object):
445
492
  )
446
493
  return None
447
494
  if not result_fields:
448
- logger.info(
495
+ logger.debug(
449
496
  "No result fields defined. Using 'FIELDS(STANDARD)' to deliver all standard fields of the object."
450
497
  )
451
498
  result_fields = ["FIELDS(STANDARD)"]
@@ -456,19 +503,21 @@ class Salesforce(object):
456
503
  query += " LIMIT {}".format(str(limit))
457
504
 
458
505
  request_header = self.request_header()
459
- request_url = f"{self._instance_url}/services/data/v52.0/query/?q={query}"
506
+ request_url = self.config()["queryUrl"] + "?q={}".format(query)
460
507
 
461
- logger.info(
508
+ logger.debug(
462
509
  "Sending query -> %s to Salesforce; calling -> %s", query, request_url
463
510
  )
464
511
 
465
512
  retries = 0
466
513
  while True:
467
- response = requests.get(request_url, headers=request_header, timeout=30)
514
+ response = requests.get(
515
+ request_url, headers=request_header, timeout=REQUEST_TIMEOUT
516
+ )
468
517
  if response.ok:
469
518
  return self.parse_request_response(response)
470
519
  elif response.status_code == 401 and retries == 0:
471
- logger.warning("Session has expired - try to re-authenticate...")
520
+ logger.debug("Session has expired - try to re-authenticate...")
472
521
  self.authenticate(revalidate=True)
473
522
  request_header = self.request_header()
474
523
  retries += 1
@@ -491,9 +540,10 @@ class Salesforce(object):
491
540
 
492
541
  Args:
493
542
  object_type (str): Type of the Salesforce business object, like "Account" or "Case".
543
+ **kwargs (dict): keyword / value ictionary with additional parameters
494
544
 
495
545
  Returns:
496
- dict | None: Dictionary with the Salesforce Case data or None if the request fails.
546
+ dict | None: Dictionary with the Salesforce object data or None if the request fails.
497
547
  """
498
548
 
499
549
  match object_type:
@@ -568,27 +618,40 @@ class Salesforce(object):
568
618
 
569
619
  # end method definition
570
620
 
571
- def get_user(self, user_id: str) -> dict | None:
572
- """Get a Salesforce user based on its ID.
621
+ def get_group_id(self, groupname: str) -> Optional[str]:
622
+ """Get a group ID by group name.
573
623
 
574
624
  Args:
575
- user_id (str): ID of the Salesforce user
625
+ groupname (str): Name of the Group.
576
626
 
577
627
  Returns:
578
- dict | None: Dictionary with the Salesforce user data or None if the request fails.
628
+ Optional[str]: Technical ID of the group
629
+ """
630
+
631
+ return self.get_object_id_by_name(
632
+ object_type="Group", name=groupname, name_field="Name"
633
+ )
634
+
635
+ # end method definition
636
+
637
+ def get_group(self, group_id: str) -> dict | None:
638
+ """Get a Salesforce group based on its ID.
639
+
640
+ Args:
641
+ group_id (str): ID of the Salesforce group
642
+
643
+ Returns:
644
+ dict | None: Dictionary with the Salesforce group data or None if the request fails.
579
645
  """
580
646
 
581
647
  if not self._access_token or not self._instance_url:
582
- logger.error("Authentication required.")
583
- return None
648
+ self.authenticate()
584
649
 
585
650
  request_header = self.request_header()
586
- request_url = (
587
- f"{self._instance_url}/services/data/v52.0/sobjects/User/{user_id}"
588
- )
651
+ request_url = self.config()["groupUrl"] + group_id
589
652
 
590
- logger.info(
591
- "Get Salesforce user with ID -> %s; calling -> %s", user_id, request_url
653
+ logger.debug(
654
+ "Get Salesforce group with ID -> %s; calling -> %s", group_id, request_url
592
655
  )
593
656
 
594
657
  retries = 0
@@ -599,14 +662,254 @@ class Salesforce(object):
599
662
  if response.ok:
600
663
  return self.parse_request_response(response)
601
664
  elif response.status_code == 401 and retries == 0:
602
- logger.warning("Session has expired - try to re-authenticate...")
665
+ logger.debug("Session has expired - try to re-authenticate...")
603
666
  self.authenticate(revalidate=True)
604
667
  request_header = self.request_header()
605
668
  retries += 1
606
669
  else:
607
670
  logger.error(
608
- "Failed to get Salesforce user -> %s; status -> %s; error -> %s",
609
- user_id,
671
+ "Failed to get Salesforce group -> %s; status -> %s; error -> %s",
672
+ group_id,
673
+ response.status_code,
674
+ response.text,
675
+ )
676
+ return None
677
+
678
+ # end method definition
679
+
680
+ def add_group(
681
+ self,
682
+ group_name: str,
683
+ group_type: str = "Regular",
684
+ ) -> dict | None:
685
+ """Add a new Salesforce group.
686
+
687
+ Args:
688
+ group_name (str): Name of the new Salesforce group
689
+
690
+ Returns:
691
+ dict | None: Dictionary with the Salesforce Group data or None if the request fails.
692
+
693
+ Example response:
694
+ {
695
+ 'id': '00GDn000000KWE0MAO',
696
+ 'success': True,
697
+ 'errors': []
698
+ }
699
+ """
700
+
701
+ if not self._access_token or not self._instance_url:
702
+ self.authenticate()
703
+
704
+ request_header = self.request_header()
705
+ request_url = self.config()["groupUrl"]
706
+
707
+ payload = {"Name": group_name, "Type": group_type}
708
+
709
+ logger.debug(
710
+ "Adding Salesforce group -> %s; calling -> %s", group_name, request_url
711
+ )
712
+
713
+ retries = 0
714
+ while True:
715
+ response = requests.post(
716
+ request_url,
717
+ headers=request_header,
718
+ data=json.dumps(payload),
719
+ timeout=REQUEST_TIMEOUT,
720
+ )
721
+ if response.ok:
722
+ return self.parse_request_response(response)
723
+ elif response.status_code == 401 and retries == 0:
724
+ logger.debug("Session has expired - try to re-authenticate...")
725
+ self.authenticate(revalidate=True)
726
+ request_header = self.request_header()
727
+ retries += 1
728
+ else:
729
+ logger.error(
730
+ "Failed to add Salesforce group -> %s; status -> %s; error -> %s",
731
+ group_name,
732
+ response.status_code,
733
+ response.text,
734
+ )
735
+ return None
736
+
737
+ # end method definition
738
+
739
+ def update_group(
740
+ self,
741
+ group_id: str,
742
+ update_data: dict,
743
+ ) -> dict:
744
+ """Update a Salesforce group.
745
+
746
+ Args:
747
+ group_id (str): The Salesforce group ID.
748
+ update_data (dict): Dictionary containing the fields to update.
749
+
750
+ Returns:
751
+ dict: Response from the Salesforce API.
752
+ """
753
+
754
+ if not self._access_token or not self._instance_url:
755
+ self.authenticate()
756
+
757
+ request_header = self.request_header()
758
+
759
+ request_url = self.config()["groupUrl"] + group_id
760
+
761
+ logger.debug(
762
+ "Update Salesforce group with ID -> %s; calling -> %s",
763
+ group_id,
764
+ request_url,
765
+ )
766
+
767
+ retries = 0
768
+ while True:
769
+ response = requests.patch(
770
+ request_url,
771
+ json=update_data,
772
+ headers=request_header,
773
+ timeout=REQUEST_TIMEOUT,
774
+ )
775
+ if response.ok:
776
+ return self.parse_request_response(response)
777
+ elif response.status_code == 401 and retries == 0:
778
+ logger.debug("Session has expired - try to re-authenticate...")
779
+ self.authenticate(revalidate=True)
780
+ request_header = self.request_header()
781
+ retries += 1
782
+ else:
783
+ logger.error(
784
+ "Failed to update Salesforce group -> %s; status -> %s; error -> %s",
785
+ group_id,
786
+ response.status_code,
787
+ response.text,
788
+ )
789
+ return None
790
+
791
+ # end method definition
792
+
793
+ def get_group_members(self, group_id: str) -> list | None:
794
+ """Get Salesforce group members
795
+
796
+ Args:
797
+ group_id (str): Id of the group to retrieve the members
798
+
799
+ Returns:
800
+ list | None: result
801
+
802
+ Example response:
803
+ {
804
+ 'totalSize': 1,
805
+ 'done': True,
806
+ 'records': [
807
+ {
808
+ 'attributes': {
809
+ 'type': 'GroupMember',
810
+ 'url': '/services/data/v60.0/sobjects/GroupMember/011Dn000000ELhwIAG'
811
+ },
812
+ 'UserOrGroupId': '00GDn000000KWE5MAO'
813
+ }
814
+ ]
815
+ }
816
+ """
817
+
818
+ if not self._access_token or not self._instance_url:
819
+ self.authenticate()
820
+
821
+ request_header = self.request_header()
822
+
823
+ request_url = self.config()["queryUrl"]
824
+
825
+ query = f"SELECT UserOrGroupId FROM GroupMember WHERE GroupId = '{group_id}'"
826
+ params = {"q": query}
827
+
828
+ logger.debug(
829
+ "Get members of Salesforce group with ID -> %s; calling -> %s",
830
+ group_id,
831
+ request_url,
832
+ )
833
+
834
+ retries = 0
835
+ while True:
836
+ response = requests.get(
837
+ request_url,
838
+ headers=request_header,
839
+ params=params,
840
+ timeout=REQUEST_TIMEOUT,
841
+ )
842
+ if response.ok:
843
+ return self.parse_request_response(response)
844
+ elif response.status_code == 401 and retries == 0:
845
+ logger.debug("Session has expired - try to re-authenticate...")
846
+ self.authenticate(revalidate=True)
847
+ request_header = self.request_header()
848
+ retries += 1
849
+ else:
850
+ logger.error(
851
+ "Failed to retrieve members of Salesforce group with ID -> %s; status -> %s; error -> %s",
852
+ group_id,
853
+ response.status_code,
854
+ response.text,
855
+ )
856
+ return None
857
+
858
+ # end method definition
859
+
860
+ def add_group_member(self, group_id: str, member_id: str) -> dict | None:
861
+ """Add a user or group to a Salesforce group
862
+
863
+ Args:
864
+ group_id (str): ID of the Salesforce Group to add member to.
865
+ member_id (str): ID of the user or group.
866
+
867
+ Returns:
868
+ dict | None: Dictionary with the Salesforce membership data or None if the request fails.
869
+
870
+ Example response (id is the membership ID):
871
+ {
872
+ 'id': '011Dn000000ELhwIAG',
873
+ 'success': True,
874
+ 'errors': []
875
+ }
876
+ """
877
+
878
+ if not self._access_token or not self._instance_url:
879
+ self.authenticate()
880
+
881
+ request_url = self.config()["groupMemberUrl"]
882
+
883
+ request_header = self.request_header()
884
+
885
+ payload = {"GroupId": group_id, "UserOrGroupId": member_id}
886
+
887
+ logger.debug(
888
+ "Add member with ID -> %s to Salesforce group with ID -> %s; calling -> %s",
889
+ member_id,
890
+ group_id,
891
+ request_url,
892
+ )
893
+
894
+ retries = 0
895
+ while True:
896
+ response = requests.post(
897
+ request_url,
898
+ headers=request_header,
899
+ json=payload,
900
+ timeout=REQUEST_TIMEOUT,
901
+ )
902
+ if response.ok:
903
+ return self.parse_request_response(response)
904
+ elif response.status_code == 401 and retries == 0:
905
+ logger.debug("Session has expired - try to re-authenticate...")
906
+ self.authenticate(revalidate=True)
907
+ request_header = self.request_header()
908
+ retries += 1
909
+ else:
910
+ logger.error(
911
+ "Failed to retrieve members of Salesforce group with ID -> %s; status -> %s; error -> %s",
912
+ group_id,
610
913
  response.status_code,
611
914
  response.text,
612
915
  )
@@ -648,11 +951,10 @@ class Salesforce(object):
648
951
  """
649
952
 
650
953
  if not self._access_token or not self._instance_url:
651
- logger.error("Authentication required.")
652
- return None
954
+ self.authenticate()
653
955
 
654
956
  request_header = self.request_header()
655
- request_url = f"{self._instance_url}/services/data/v52.0/query/"
957
+ request_url = self.config()["queryUrl"]
656
958
 
657
959
  query = "SELECT Id, Name, CreatedById, CreatedDate, Description, LastModifiedById, LastModifiedDate, PermissionsCustomizeApplication, PermissionsEditTask, PermissionsImportLeads FROM Profile"
658
960
 
@@ -667,7 +969,7 @@ class Salesforce(object):
667
969
  if response.ok:
668
970
  return self.parse_request_response(response)
669
971
  elif response.status_code == 401 and retries == 0:
670
- logger.warning("Session has expired - try to re-authenticate...")
972
+ logger.debug("Session has expired - try to re-authenticate...")
671
973
  self.authenticate(revalidate=True)
672
974
  request_header = self.request_header()
673
975
  retries += 1
@@ -681,27 +983,112 @@ class Salesforce(object):
681
983
 
682
984
  # end method definition
683
985
 
986
+ def get_user_profile_id(self, profile_name: str) -> Optional[str]:
987
+ """Get a user profile ID by profile name.
988
+
989
+ Args:
990
+ profile_name (str): Name of the User Profile.
991
+
992
+ Returns:
993
+ Optional[str]: Technical ID of the user profile.
994
+ """
995
+
996
+ return self.get_object_id_by_name(object_type="Profile", name=profile_name)
997
+
998
+ # end method definition
999
+
1000
+ def get_user_id(self, username: str) -> Optional[str]:
1001
+ """Get a user ID by user name.
1002
+
1003
+ Args:
1004
+ username (str): Name of the User.
1005
+
1006
+ Returns:
1007
+ Optional[str]: Technical ID of the user
1008
+ """
1009
+
1010
+ return self.get_object_id_by_name(
1011
+ object_type="User", name=username, name_field="Username"
1012
+ )
1013
+
1014
+ # end method definition
1015
+
1016
+ def get_user(self, user_id: str) -> dict | None:
1017
+ """Get a Salesforce user based on its ID.
1018
+
1019
+ Args:
1020
+ user_id (str): ID of the Salesforce user
1021
+
1022
+ Returns:
1023
+ dict | None: Dictionary with the Salesforce user data or None if the request fails.
1024
+ """
1025
+
1026
+ if not self._access_token or not self._instance_url:
1027
+ self.authenticate()
1028
+
1029
+ request_header = self.request_header()
1030
+ request_url = self.config()["userUrl"] + user_id
1031
+
1032
+ logger.debug(
1033
+ "Get Salesforce user with ID -> %s; calling -> %s", user_id, request_url
1034
+ )
1035
+
1036
+ retries = 0
1037
+ while True:
1038
+ response = requests.get(
1039
+ request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1040
+ )
1041
+ if response.ok:
1042
+ return self.parse_request_response(response)
1043
+ elif response.status_code == 401 and retries == 0:
1044
+ logger.debug("Session has expired - try to re-authenticate...")
1045
+ self.authenticate(revalidate=True)
1046
+ request_header = self.request_header()
1047
+ retries += 1
1048
+ else:
1049
+ logger.error(
1050
+ "Failed to get Salesforce user -> %s; status -> %s; error -> %s",
1051
+ user_id,
1052
+ response.status_code,
1053
+ response.text,
1054
+ )
1055
+ return None
1056
+
1057
+ # end method definition
1058
+
684
1059
  def add_user(
685
1060
  self,
686
1061
  username: str,
687
1062
  email: str,
688
- password: str,
689
1063
  firstname: str,
690
1064
  lastname: str,
1065
+ title: str | None = None,
1066
+ department: str | None = None,
1067
+ company_name: str = "Innovate",
1068
+ profile_name: Optional[str] = "Standard User",
691
1069
  profile_id: Optional[str] = None,
1070
+ time_zone_key: Optional[str] = "America/Los_Angeles",
1071
+ email_encoding_key: Optional[str] = "ISO-8859-1",
1072
+ locale_key: Optional[str] = "en_US",
692
1073
  alias: Optional[str] = None,
693
1074
  ) -> dict | None:
694
- """Add a new Salesforce user.
1075
+ """Add a new Salesforce user. The password has to be set separately.
695
1076
 
696
1077
  Args:
697
1078
  username (str): Login name of the new user
698
1079
  email (str): Email of the new user
699
- password (str): Password of the new user
700
1080
  firstname (str): First name of the new user.
701
1081
  lastname (str): Last name of the new user.
1082
+ title (str): Title of the user.
1083
+ department (str): Department of the user.
1084
+ company_name (str): Name of the Company of the user.
1085
+ profile_name (str): Profile name like "Standard User"
702
1086
  profile_id (str, optional): Profile ID of the new user. Defaults to None.
703
1087
  Use method get_all_user_profiles() to determine
704
- the desired Profile for the user.
1088
+ the desired Profile for the user. Or pass the profile_name.
1089
+ time_zone_key (str, optional) in format country/city like "America/Los_Angeles",
1090
+ email_encoding_key (str, optional). Default is "ISO-8859-1".
1091
+ locale_key (str, optional). Default is "en_US".
705
1092
  alias (str, optional): Alias of the new user. Defaults to None.
706
1093
 
707
1094
  Returns:
@@ -709,23 +1096,32 @@ class Salesforce(object):
709
1096
  """
710
1097
 
711
1098
  if not self._access_token or not self._instance_url:
712
- logger.error("Authentication required.")
713
- return None
1099
+ self.authenticate()
714
1100
 
715
1101
  request_header = self.request_header()
716
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/User/"
1102
+ request_url = self.config()["userUrl"]
1103
+
1104
+ # if just a profile name is given then we determine the profile ID by the name:
1105
+ if profile_name and not profile_id:
1106
+ profile_id = self.get_user_profile_id(profile_name)
717
1107
 
718
1108
  payload = {
719
1109
  "Username": username,
720
1110
  "Email": email,
721
- "Password": password,
722
1111
  "FirstName": firstname,
723
1112
  "LastName": lastname,
724
1113
  "ProfileId": profile_id,
725
- "Alias": alias,
1114
+ "Department": department,
1115
+ "CompanyName": company_name,
1116
+ "Title": title,
1117
+ "Alias": alias if alias else username,
1118
+ "TimeZoneSidKey": time_zone_key, # Set default TimeZoneSidKey
1119
+ "LocaleSidKey": locale_key, # Set default LocaleSidKey
1120
+ "EmailEncodingKey": email_encoding_key, # Set default EmailEncodingKey
1121
+ "LanguageLocaleKey": locale_key, # Set default LanguageLocaleKey
726
1122
  }
727
1123
 
728
- logger.info(
1124
+ logger.debug(
729
1125
  "Adding Salesforce user -> %s; calling -> %s", username, request_url
730
1126
  )
731
1127
 
@@ -740,7 +1136,7 @@ class Salesforce(object):
740
1136
  if response.ok:
741
1137
  return self.parse_request_response(response)
742
1138
  elif response.status_code == 401 and retries == 0:
743
- logger.warning("Session has expired - try to re-authenticate...")
1139
+ logger.debug("Session has expired - try to re-authenticate...")
744
1140
  self.authenticate(revalidate=True)
745
1141
  request_header = self.request_header()
746
1142
  retries += 1
@@ -755,6 +1151,193 @@ class Salesforce(object):
755
1151
 
756
1152
  # end method definition
757
1153
 
1154
+ def update_user(
1155
+ self,
1156
+ user_id: str,
1157
+ update_data: dict,
1158
+ ) -> dict:
1159
+ """Update a Salesforce user.
1160
+
1161
+ Args:
1162
+ user_id (str): The Salesforce user ID.
1163
+ update_data (dict): Dictionary containing the fields to update.
1164
+
1165
+ Returns:
1166
+ dict: Response from the Salesforce API.
1167
+ """
1168
+
1169
+ if not self._access_token or not self._instance_url:
1170
+ self.authenticate()
1171
+
1172
+ request_header = self.request_header()
1173
+
1174
+ request_url = self.config()["userUrl"] + user_id
1175
+
1176
+ logger.debug(
1177
+ "Update Salesforce user with ID -> %s; calling -> %s", user_id, request_url
1178
+ )
1179
+
1180
+ retries = 0
1181
+ while True:
1182
+ response = requests.patch(
1183
+ request_url,
1184
+ json=update_data,
1185
+ headers=request_header,
1186
+ timeout=REQUEST_TIMEOUT,
1187
+ )
1188
+ if response.ok:
1189
+ return self.parse_request_response(response)
1190
+ elif response.status_code == 401 and retries == 0:
1191
+ logger.debug("Session has expired - try to re-authenticate...")
1192
+ self.authenticate(revalidate=True)
1193
+ request_header = self.request_header()
1194
+ retries += 1
1195
+ else:
1196
+ logger.error(
1197
+ "Failed to update Salesforce user -> %s; status -> %s; error -> %s",
1198
+ user_id,
1199
+ response.status_code,
1200
+ response.text,
1201
+ )
1202
+ return None
1203
+
1204
+ # end method definition
1205
+
1206
+ def update_user_password(
1207
+ self,
1208
+ user_id: str,
1209
+ password: str,
1210
+ ) -> dict:
1211
+ """Update the password of a Salesforce user.
1212
+
1213
+ Args:
1214
+ user_id (str): The Salesforce user ID.
1215
+ password (str): New user password.
1216
+
1217
+ Returns:
1218
+ dict: Response from the Salesforce API.
1219
+ """
1220
+
1221
+ if not self._access_token or not self._instance_url:
1222
+ self.authenticate()
1223
+
1224
+ request_header = self.request_header()
1225
+
1226
+ request_url = self.config()["userUrl"] + "{}/password".format(user_id)
1227
+
1228
+ logger.debug(
1229
+ "Update password of Salesforce user with ID -> %s; calling -> %s",
1230
+ user_id,
1231
+ request_url,
1232
+ )
1233
+
1234
+ update_data = {"NewPassword": password}
1235
+
1236
+ retries = 0
1237
+ while True:
1238
+ response = requests.post(
1239
+ request_url,
1240
+ json=update_data,
1241
+ headers=request_header,
1242
+ timeout=REQUEST_TIMEOUT,
1243
+ )
1244
+ if response.ok:
1245
+ return self.parse_request_response(response)
1246
+ elif response.status_code == 401 and retries == 0:
1247
+ logger.debug("Session has expired - try to re-authenticate...")
1248
+ self.authenticate(revalidate=True)
1249
+ request_header = self.request_header()
1250
+ retries += 1
1251
+ else:
1252
+ logger.error(
1253
+ "Failed to update password of Salesforce user -> %s; status -> %s; error -> %s",
1254
+ user_id,
1255
+ response.status_code,
1256
+ response.text,
1257
+ )
1258
+ return None
1259
+
1260
+ # end method definition
1261
+
1262
+ def update_user_photo(
1263
+ self,
1264
+ user_id: str,
1265
+ photo_path: str,
1266
+ ) -> dict | None:
1267
+ """Update the Salesforce user photo.
1268
+
1269
+ Args:
1270
+ user_id (str): Salesforce ID of the user
1271
+ photo_path (str): file system path with the location of the photo
1272
+ Returns:
1273
+ dict | None: Dictionary with the Salesforce User data or None if the request fails.
1274
+ """
1275
+
1276
+ if not self._access_token or not self._instance_url:
1277
+ self.authenticate()
1278
+
1279
+ # Check if the photo file exists
1280
+ if not os.path.isfile(photo_path):
1281
+ logger.error("Photo file -> %s not found!", photo_path)
1282
+ return None
1283
+
1284
+ try:
1285
+ # Read the photo file as binary data
1286
+ with open(photo_path, "rb") as image_file:
1287
+ photo_data = image_file.read()
1288
+ except OSError as exception:
1289
+ # Handle any errors that occurred while reading the photo file
1290
+ logger.error(
1291
+ "Error reading photo file -> %s; error -> %s", photo_path, exception
1292
+ )
1293
+ return None
1294
+
1295
+ request_header = self.request_header(content_type=None)
1296
+
1297
+ data = {"json": json.dumps({"cropX": 0, "cropY": 0, "cropSize": 200})}
1298
+ request_url = self.config()["connectUrl"] + f"user-profiles/{user_id}/photo"
1299
+ files = {
1300
+ "fileUpload": (
1301
+ photo_path,
1302
+ photo_data,
1303
+ "application/octet-stream",
1304
+ )
1305
+ }
1306
+
1307
+ logger.debug(
1308
+ "Update profile photo of Salesforce user with ID -> %s; calling -> %s",
1309
+ user_id,
1310
+ request_url,
1311
+ )
1312
+
1313
+ retries = 0
1314
+ while True:
1315
+ response = requests.post(
1316
+ request_url,
1317
+ files=files,
1318
+ data=data,
1319
+ headers=request_header,
1320
+ verify=False,
1321
+ timeout=REQUEST_TIMEOUT,
1322
+ )
1323
+ if response.ok:
1324
+ return self.parse_request_response(response)
1325
+ elif response.status_code == 401 and retries == 0:
1326
+ logger.debug("Session has expired - try to re-authenticate...")
1327
+ self.authenticate(revalidate=True)
1328
+ request_header = self.request_header()
1329
+ retries += 1
1330
+ else:
1331
+ logger.error(
1332
+ "Failed to update profile photo of Salesforce user with ID -> %s; status -> %s; error -> %s",
1333
+ user_id,
1334
+ response.status_code,
1335
+ response.text,
1336
+ )
1337
+ return None
1338
+
1339
+ # end method definition
1340
+
758
1341
  def add_account(
759
1342
  self,
760
1343
  account_name: str,
@@ -783,11 +1366,10 @@ class Salesforce(object):
783
1366
  """
784
1367
 
785
1368
  if not self._access_token or not self._instance_url:
786
- logger.error("Authentication required.")
787
- return None
1369
+ self.authenticate()
788
1370
 
789
1371
  request_header = self.request_header()
790
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Account/"
1372
+ request_url = self.config()["accountUrl"]
791
1373
 
792
1374
  payload = {
793
1375
  "Name": account_name,
@@ -800,7 +1382,7 @@ class Salesforce(object):
800
1382
  }
801
1383
  payload.update(kwargs) # Add additional fields from kwargs
802
1384
 
803
- logger.info(
1385
+ logger.debug(
804
1386
  "Adding Salesforce account -> %s; calling -> %s", account_name, request_url
805
1387
  )
806
1388
 
@@ -815,7 +1397,7 @@ class Salesforce(object):
815
1397
  if response.ok:
816
1398
  return self.parse_request_response(response)
817
1399
  elif response.status_code == 401 and retries == 0:
818
- logger.warning("Session has expired - try to re-authenticate...")
1400
+ logger.debug("Session has expired - try to re-authenticate...")
819
1401
  self.authenticate(revalidate=True)
820
1402
  request_header = self.request_header()
821
1403
  retries += 1
@@ -851,11 +1433,10 @@ class Salesforce(object):
851
1433
  """
852
1434
 
853
1435
  if not self._access_token or not self._instance_url:
854
- logger.error("Authentication required.")
855
- return None
1436
+ self.authenticate()
856
1437
 
857
1438
  request_header = self.request_header()
858
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Product2/"
1439
+ request_url = self.config()["productUrl"]
859
1440
 
860
1441
  payload = {
861
1442
  "Name": product_name,
@@ -865,7 +1446,7 @@ class Salesforce(object):
865
1446
  }
866
1447
  payload.update(kwargs) # Add additional fields from kwargs
867
1448
 
868
- logger.info(
1449
+ logger.debug(
869
1450
  "Add Salesforce product -> %s; calling -> %s", product_name, request_url
870
1451
  )
871
1452
 
@@ -880,7 +1461,7 @@ class Salesforce(object):
880
1461
  if response.ok:
881
1462
  return self.parse_request_response(response)
882
1463
  elif response.status_code == 401 and retries == 0:
883
- logger.warning("Session has expired - try to re-authenticate...")
1464
+ logger.debug("Session has expired - try to re-authenticate...")
884
1465
  self.authenticate(revalidate=True)
885
1466
  request_header = self.request_header()
886
1467
  retries += 1
@@ -922,11 +1503,10 @@ class Salesforce(object):
922
1503
  """
923
1504
 
924
1505
  if not self._access_token or not self._instance_url:
925
- logger.error("Authentication required.")
926
- return None
1506
+ self.authenticate()
927
1507
 
928
1508
  request_header = self.request_header()
929
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Opportunity/"
1509
+ request_url = self.config()["opportunityUrl"]
930
1510
 
931
1511
  payload = {
932
1512
  "Name": name,
@@ -939,7 +1519,7 @@ class Salesforce(object):
939
1519
  payload["Description"] = description
940
1520
  payload.update(kwargs) # Add additional fields from kwargs
941
1521
 
942
- logger.info(
1522
+ logger.debug(
943
1523
  "Add Salesforce opportunity -> %s; calling -> %s", name, request_url
944
1524
  )
945
1525
 
@@ -954,7 +1534,7 @@ class Salesforce(object):
954
1534
  if response.ok:
955
1535
  return self.parse_request_response(response)
956
1536
  elif response.status_code == 401 and retries == 0:
957
- logger.warning("Session has expired - try to re-authenticate...")
1537
+ logger.debug("Session has expired - try to re-authenticate...")
958
1538
  self.authenticate(revalidate=True)
959
1539
  request_header = self.request_header()
960
1540
  retries += 1
@@ -967,6 +1547,8 @@ class Salesforce(object):
967
1547
  )
968
1548
  return None
969
1549
 
1550
+ # end method definition
1551
+
970
1552
  def add_case(
971
1553
  self,
972
1554
  subject: str,
@@ -990,6 +1572,7 @@ class Salesforce(object):
990
1572
  priority (str): Priority of the case. Typical values: "High", "Medium", "Low".
991
1573
  origin (str): origin (source) of the case. Typical values: "Email", "Phone", "Web"
992
1574
  account_id (str): technical ID of the related Account
1575
+ owner_id (str): owner of the case
993
1576
  asset_id (str): technical ID of the related Asset
994
1577
  product_id (str): technical ID of the related Product
995
1578
  kwargs (Any): additional values (e.g. custom fields)
@@ -999,11 +1582,10 @@ class Salesforce(object):
999
1582
  """
1000
1583
 
1001
1584
  if not self._access_token or not self._instance_url:
1002
- logger.error("Authentication required.")
1003
- return None
1585
+ self.authenticate()
1004
1586
 
1005
1587
  request_header = self.request_header()
1006
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Case/"
1588
+ request_url = self.config()["caseUrl"]
1007
1589
 
1008
1590
  payload = {
1009
1591
  "Subject": subject,
@@ -1021,7 +1603,7 @@ class Salesforce(object):
1021
1603
  payload["ProductId"] = product_id
1022
1604
  payload.update(kwargs) # Add additional fields from kwargs
1023
1605
 
1024
- logger.info("Add Salesforce case -> %s; calling -> %s", subject, request_url)
1606
+ logger.debug("Add Salesforce case -> %s; calling -> %s", subject, request_url)
1025
1607
 
1026
1608
  retries = 0
1027
1609
  while True:
@@ -1034,7 +1616,7 @@ class Salesforce(object):
1034
1616
  if response.ok:
1035
1617
  return self.parse_request_response(response)
1036
1618
  elif response.status_code == 401 and retries == 0:
1037
- logger.warning("Session has expired - try to re-authenticate...")
1619
+ logger.debug("Session has expired - try to re-authenticate...")
1038
1620
  self.authenticate(revalidate=True)
1039
1621
  request_header = self.request_header()
1040
1622
  retries += 1
@@ -1077,11 +1659,10 @@ class Salesforce(object):
1077
1659
  """
1078
1660
 
1079
1661
  if not self._access_token or not self._instance_url:
1080
- logger.error("Authentication required.")
1081
- return None
1662
+ self.authenticate()
1082
1663
 
1083
1664
  request_header = self.request_header()
1084
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Asset/"
1665
+ request_url = self.config()["assetUrl"]
1085
1666
 
1086
1667
  payload = {
1087
1668
  "Name": asset_name,
@@ -1095,7 +1676,7 @@ class Salesforce(object):
1095
1676
  payload["Description"] = description
1096
1677
  payload.update(kwargs) # Add additional fields from kwargs
1097
1678
 
1098
- logger.info(
1679
+ logger.debug(
1099
1680
  "Add Salesforce asset -> %s; calling -> %s", asset_name, request_url
1100
1681
  )
1101
1682
 
@@ -1110,7 +1691,7 @@ class Salesforce(object):
1110
1691
  if response.ok:
1111
1692
  return self.parse_request_response(response)
1112
1693
  elif response.status_code == 401 and retries == 0:
1113
- logger.warning("Session has expired - try to re-authenticate...")
1694
+ logger.debug("Session has expired - try to re-authenticate...")
1114
1695
  self.authenticate(revalidate=True)
1115
1696
  request_header = self.request_header()
1116
1697
  retries += 1
@@ -1151,11 +1732,10 @@ class Salesforce(object):
1151
1732
  """
1152
1733
 
1153
1734
  if not self._access_token or not self._instance_url:
1154
- logger.error("Authentication required.")
1155
- return None
1735
+ self.authenticate()
1156
1736
 
1157
1737
  request_header = self.request_header()
1158
- request_url = f"{self._instance_url}/services/data/v52.0/sobjects/Contract/"
1738
+ request_url = self.config()["contractUrl"]
1159
1739
 
1160
1740
  payload = {
1161
1741
  "AccountId": account_id,
@@ -1169,7 +1749,7 @@ class Salesforce(object):
1169
1749
  payload["ContractType"] = contract_type
1170
1750
  payload.update(kwargs) # Add additional fields from kwargs
1171
1751
 
1172
- logger.info(
1752
+ logger.debug(
1173
1753
  "Adding Salesforce contract for account ID -> %s; calling -> %s",
1174
1754
  account_id,
1175
1755
  request_url,
@@ -1186,7 +1766,7 @@ class Salesforce(object):
1186
1766
  if response.ok:
1187
1767
  return self.parse_request_response(response)
1188
1768
  elif response.status_code == 401 and retries == 0:
1189
- logger.warning("Session has expired - try to re-authenticate...")
1769
+ logger.debug("Session has expired - try to re-authenticate...")
1190
1770
  self.authenticate(revalidate=True)
1191
1771
  request_header = self.request_header()
1192
1772
  retries += 1