p4cmd 2.4.12__tar.gz → 2.5.0__tar.gz

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.
Files changed (24) hide show
  1. {p4cmd-2.4.12 → p4cmd-2.5.0}/PKG-INFO +2 -2
  2. {p4cmd-2.4.12 → p4cmd-2.5.0}/README.md +1 -1
  3. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/__init__.py +1 -1
  4. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4cmd.py +26 -49
  5. p4cmd-2.5.0/p4cmd/utils.py +77 -0
  6. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/PKG-INFO +2 -2
  7. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/SOURCES.txt +1 -0
  8. {p4cmd-2.4.12 → p4cmd-2.5.0}/LICENSE +0 -0
  9. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4errors.py +0 -0
  10. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4file.py +0 -0
  11. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/__init__.py +0 -0
  12. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_DEPOT_ONLY.png +0 -0
  13. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_MOVED.png +0 -0
  14. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_NEED_SYNC.png +0 -0
  15. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_ADD.png +0 -0
  16. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_DELETE.png +0 -0
  17. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_EDIT.png +0 -0
  18. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UNKNOWN.png +0 -0
  19. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UNTRACKED.png +0 -0
  20. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UP_TO_DATE.png +0 -0
  21. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/dependency_links.txt +0 -0
  22. {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/top_level.txt +0 -0
  23. {p4cmd-2.4.12 → p4cmd-2.5.0}/setup.cfg +0 -0
  24. {p4cmd-2.4.12 → p4cmd-2.5.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p4cmd
3
- Version: 2.4.12
3
+ Version: 2.5.0
4
4
  Summary: Simple P4 python module
5
5
  Home-page: https://github.com/nielsvaes/p4cmd
6
6
  Author: Niels Vaes
@@ -50,7 +50,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
50
50
  client = p4cmd.P4Client.from_env()
51
51
  ```
52
52
 
53
- Most of the functions are pretty self explanatory and have docstrings about how they work.
53
+ Most of the functions are pretty self-explanatory and have docstrings about how they work.
54
54
 
55
55
  There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
56
56
 
@@ -29,7 +29,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
29
29
  client = p4cmd.P4Client.from_env()
30
30
  ```
31
31
 
32
- Most of the functions are pretty self explanatory and have docstrings about how they work.
32
+ Most of the functions are pretty self-explanatory and have docstrings about how they work.
33
33
 
34
34
  There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
35
35
 
@@ -1,4 +1,4 @@
1
- VERSION = "2.4.12"
1
+ VERSION = "2.5.0"
2
2
 
3
3
  from .p4cmd import P4Client
4
4
  from .p4file import P4File
@@ -9,6 +9,7 @@ import logging
9
9
 
10
10
  from . import p4errors
11
11
  from .p4file import P4File, Status
12
+ from .utils import split_list_into_strings_of_length, convert_to_list, decode_dictionaries, validate_not_empty
12
13
 
13
14
  MAX_CMD_LEN = 8190
14
15
  MAX_ARG_LEN = 8000 # max length of args string when combined, close to max, but leaving some extra margin
@@ -206,6 +207,7 @@ class P4Client(object):
206
207
  else:
207
208
  raise p4errors.WorkSpaceError("Tried to set a workspace/client({}) that did not exist".format(workspace))
208
209
 
210
+ @validate_not_empty
209
211
  def files_to_p4files(self, file_list, allow_invalid_files=False):
210
212
  """
211
213
  Turns a list of files into P4File objects. If the Perforce server can't be reached, returns a list of P4Files
@@ -232,6 +234,7 @@ class P4Client(object):
232
234
  p4files.append(perforce_file)
233
235
  return p4files
234
236
 
237
+ @validate_not_empty
235
238
  def folder_to_p4files(self, folder, include_subfolders=True, allow_invalid_files=False):
236
239
  """
237
240
  Returns all the files in a folder as a list of P4File objects. Uses the files_to_p4files function if the host
@@ -301,7 +304,8 @@ class P4Client(object):
301
304
  if len(changelists):
302
305
  return True
303
306
  return False
304
-
307
+
308
+ @validate_not_empty
305
309
  def move_files_to_changelist(self, file_list, changelist="default"):
306
310
  """
307
311
  Moves the files in file_list to a changelist. Makes a new changelist if the given one doesn't exist.
@@ -371,6 +375,7 @@ class P4Client(object):
371
375
  return True
372
376
  return False
373
377
 
378
+ @validate_not_empty
374
379
  def revert_files(self, file_list, unchanged_only=False):
375
380
  """
376
381
  Reverts files in file_list
@@ -389,6 +394,8 @@ class P4Client(object):
389
394
 
390
395
  return info_dicts
391
396
 
397
+
398
+ @validate_not_empty
392
399
  def revert_folders(self, folder_list, unchanged_only=False):
393
400
  """
394
401
  Recursively reverts complete folders
@@ -442,6 +449,7 @@ class P4Client(object):
442
449
  info_dicts = self.run_cmd("submit", args=["-c", changelist, "--parallel", f"threads={self.max_parallel_connections}"])
443
450
  return info_dicts
444
451
 
452
+ @validate_not_empty
445
453
  def sync_folders(self, folder_list):
446
454
  """
447
455
  Recursively syncs complete folders
@@ -463,6 +471,7 @@ class P4Client(object):
463
471
  info_dicts = self.run_cmd("sync", args=["--parallel", f"threads={self.max_parallel_connections}"], file_list=cleaned_folder_list)
464
472
  return info_dicts
465
473
 
474
+ @validate_not_empty
466
475
  def sync_files(self, file_list, revision=-1, verify=True, force=False):
467
476
  """
468
477
  Syncs files
@@ -493,6 +502,7 @@ class P4Client(object):
493
502
 
494
503
  return info_dicts
495
504
 
505
+ @validate_not_empty
496
506
  def reconcile_offline_files(self, file_list, changelist="default"):
497
507
  """
498
508
  Adds, opens for edit or delete any files that were changed outside Perforce
@@ -508,6 +518,7 @@ class P4Client(object):
508
518
  info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=file_list)
509
519
  return info_dicts
510
520
 
521
+ @validate_not_empty
511
522
  def reconcile_offline_folders(self, folder_list, changelist="default"):
512
523
  """
513
524
  Adds, opens for edit or delete any files in the rootfolder + subfolders that were changed outside Perforce
@@ -532,6 +543,7 @@ class P4Client(object):
532
543
  info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=cleaned_folder_list)
533
544
  return info_dicts
534
545
 
546
+ @validate_not_empty
535
547
  def delete_files(self, file_list, changelist="default"):
536
548
  """
537
549
  Marks files for delete
@@ -582,6 +594,7 @@ class P4Client(object):
582
594
 
583
595
  return files_and_cl
584
596
 
597
+ @validate_not_empty
585
598
  def add_or_edit_folders(self, folders, include_subfolders=True, changelist="default"):
586
599
  """
587
600
  Marks a folder for add or edit
@@ -605,6 +618,8 @@ class P4Client(object):
605
618
 
606
619
  return self.add_or_edit_files(all_files, changelist=changelist)
607
620
 
621
+
622
+ @validate_not_empty
608
623
  def add_or_edit_files(self, file_list, changelist="default"):
609
624
  """
610
625
  Marks the files in file_list for add if they are new, or edit if they are already versioned
@@ -653,6 +668,7 @@ class P4Client(object):
653
668
  return int(self.__get_dict_value(info_dict, "change"))
654
669
  return -1
655
670
 
671
+ @validate_not_empty
656
672
  def edit_files(self, file_list, changelist="default"):
657
673
  """
658
674
  Marks the files in file_list for edit
@@ -673,6 +689,7 @@ class P4Client(object):
673
689
  logging.error(self.__get_dict_value(info_dict, "data"))
674
690
  return info_dicts
675
691
 
692
+ @validate_not_empty
676
693
  def add_files(self, file_list, changelist="default"):
677
694
  """
678
695
  Marks the files in file_list for add
@@ -738,7 +755,10 @@ class P4Client(object):
738
755
 
739
756
  for info_dict in info_dicts:
740
757
  description_filter = description_filter.rstrip("\n")
741
- cl_description = self.__get_dict_value(info_dict, "desc", "").rstrip("\n")
758
+ try:
759
+ cl_description = self.__get_dict_value(info_dict, "desc").rstrip("\n")
760
+ except AttributeError as err:
761
+ raise p4errors.P4cmdError(err)
742
762
 
743
763
  if not cl_description:
744
764
  logging.warning(f"The CL description is empty in this return object!\n{pformat(info_dict)}")
@@ -805,6 +825,7 @@ class P4Client(object):
805
825
 
806
826
  return workspaces
807
827
 
828
+ @validate_not_empty
808
829
  def get_depot_paths(self, paths):
809
830
  """
810
831
  Returns the depot path of the given files or folders, based on the client
@@ -824,6 +845,8 @@ class P4Client(object):
824
845
  depot_paths = [self.__get_dict_value(info, "depotFile").rstrip("/...") for info in info_dicts]
825
846
  return depot_paths
826
847
 
848
+
849
+ @validate_not_empty
827
850
  def get_local_paths(self, paths):
828
851
  """
829
852
  Returns the local path of the given files or folders, based on the client
@@ -844,6 +867,7 @@ class P4Client(object):
844
867
  local_paths = [self.__get_dict_value(info, "path") for info in info_dicts]
845
868
  return local_paths
846
869
 
870
+ @validate_not_empty
847
871
  def get_history(self, paths):
848
872
  """
849
873
  Returns changes for the given paths
@@ -1018,50 +1042,3 @@ class P4Client(object):
1018
1042
  if not f.lower().startswith(self.perforce_root.lower()):
1019
1043
  raise Exception(f'{f} is not under perforce root: {self.perforce_root}')
1020
1044
 
1021
-
1022
- def split_list_into_strings_of_length(input_list, max_length=100):
1023
- """
1024
- From incoming list of strings, build joined together strings that are clamped to a max size
1025
-
1026
- :param input_list:
1027
- :param max_length: str clamp length
1028
- """
1029
- full_list_str = " ".join([str(arg) for arg in input_list])
1030
-
1031
- # if str is already below max length, just return it wrapped in a list
1032
- if not len(full_list_str) > max_length:
1033
- return [full_list_str]
1034
-
1035
- # Keep appending on the last string until we hit the max length
1036
- # then add a new entry to the list and keep appending
1037
- clamped_str_list = [""]
1038
- for arg in input_list:
1039
- current_str = clamped_str_list[-1]
1040
- new_str = "{}{} ".format(current_str, arg) # the space is deliberately placed, since the first item has no len
1041
-
1042
- # if new string is too long, just append the arg as a new item and skip to next arg
1043
- if len(new_str) >= max_length:
1044
- clamped_str_list.append("{} ".format(arg)) #
1045
- continue
1046
-
1047
- # set latest to updated string
1048
- clamped_str_list[-1] = new_str
1049
-
1050
- return clamped_str_list
1051
-
1052
- def decode_dictionaries(info_dicts):
1053
- """
1054
- Decode list of dictionary keys and values into unicode from bytes
1055
- """
1056
- decoded_dicts = []
1057
- for info_dict in info_dicts:
1058
- decoded_dict = {k.decode(): v.decode() for k, v in info_dict.items()}
1059
- decoded_dicts.append(decoded_dict)
1060
- return decoded_dicts
1061
-
1062
- def convert_to_list(value):
1063
- if isinstance(value, tuple):
1064
- converted = [v for v in value]
1065
- else:
1066
- converted = [value]
1067
- return converted
@@ -0,0 +1,77 @@
1
+ import logging
2
+
3
+ def split_list_into_strings_of_length(input_list, max_length=100):
4
+ """
5
+ From incoming list of strings, build joined together strings that are clamped to a max size
6
+
7
+ :param input_list:
8
+ :param max_length: str clamp length
9
+ """
10
+ full_list_str = " ".join([str(arg) for arg in input_list])
11
+
12
+ # if str is already below max length, just return it wrapped in a list
13
+ if not len(full_list_str) > max_length:
14
+ return [full_list_str]
15
+
16
+ # Keep appending on the last string until we hit the max length
17
+ # then add a new entry to the list and keep appending
18
+ clamped_str_list = [""]
19
+ for arg in input_list:
20
+ current_str = clamped_str_list[-1]
21
+ new_str = "{}{} ".format(current_str, arg) # the space is deliberately placed, since the first item has no len
22
+
23
+ # if new string is too long, just append the arg as a new item and skip to next arg
24
+ if len(new_str) >= max_length:
25
+ clamped_str_list.append("{} ".format(arg)) #
26
+ continue
27
+
28
+ # set latest to updated string
29
+ clamped_str_list[-1] = new_str
30
+
31
+ return clamped_str_list
32
+
33
+
34
+ def decode_dictionaries(info_dicts):
35
+ """
36
+ Decode list of dictionary keys and values into unicode from bytes
37
+ """
38
+ decoded_dicts = []
39
+ for info_dict in info_dicts:
40
+ decoded_dict = {k.decode(): v.decode() for k, v in info_dict.items()}
41
+ decoded_dicts.append(decoded_dict)
42
+ return decoded_dicts
43
+
44
+
45
+ def convert_to_list(value):
46
+ """
47
+ Converts a value to a list if it isn't already one.
48
+ :param value: input value
49
+ :return: list
50
+ """
51
+ if isinstance(value, tuple):
52
+ converted = [v for v in value]
53
+ else:
54
+ converted = [value]
55
+ return converted
56
+
57
+
58
+ def validate_not_empty(func):
59
+ """
60
+ Decorator to ensure file and folder lists are not empty before proceeding with Perforce operations
61
+ """
62
+
63
+ def wrapper(*args, **kwargs):
64
+ file_or_folder_list = args[1] if len(args) > 1 else None
65
+
66
+ if file_or_folder_list is not None and not isinstance(file_or_folder_list, list):
67
+ file_or_folder_list = convert_to_list(file_or_folder_list)
68
+
69
+ if not file_or_folder_list:
70
+ logging.warning(f"Empty file list provided to {func.__name__}, operation skipped.")
71
+ logging.warning(f"args: {args}")
72
+ logging.warning(f"kwargs: {kwargs}")
73
+ return []
74
+
75
+ return func(*args, **kwargs)
76
+
77
+ return wrapper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p4cmd
3
- Version: 2.4.12
3
+ Version: 2.5.0
4
4
  Summary: Simple P4 python module
5
5
  Home-page: https://github.com/nielsvaes/p4cmd
6
6
  Author: Niels Vaes
@@ -50,7 +50,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
50
50
  client = p4cmd.P4Client.from_env()
51
51
  ```
52
52
 
53
- Most of the functions are pretty self explanatory and have docstrings about how they work.
53
+ Most of the functions are pretty self-explanatory and have docstrings about how they work.
54
54
 
55
55
  There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
56
56
 
@@ -5,6 +5,7 @@ p4cmd/__init__.py
5
5
  p4cmd/p4cmd.py
6
6
  p4cmd/p4errors.py
7
7
  p4cmd/p4file.py
8
+ p4cmd/utils.py
8
9
  p4cmd.egg-info/PKG-INFO
9
10
  p4cmd.egg-info/SOURCES.txt
10
11
  p4cmd.egg-info/dependency_links.txt
File without changes
File without changes
File without changes
File without changes
File without changes