Coverage for pass_import/managers/csv.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 12:11 +0000

1# -*- encoding: utf-8 -*- 

2# pass import - Passwords importer swiss army knife 

3# Copyright (C) 2017-2024 Alexandre PUJOL <alexandre@pujol.io>. 

4# 

5 

6import csv 

7import os 

8 

9from pass_import.core import Cap, register_managers 

10from pass_import.errors import FormatError, PMError 

11from pass_import.formats.csv import CSV 

12from pass_import.manager import PasswordExporter 

13 

14 

15class GenericCSV(CSV, PasswordExporter): 

16 """Importer & Exporter in generic CSV format. 

17 

18 :usage: 

19 You should use the --cols option to map columns to credential attributes. 

20 The recognized column names by pass-import are the following: 

21 'title', 'password', 'login', 'email', 'url', 'comments', 

22 'otpauth', 'group' 

23 ``title`` and ``group`` field are used to generate the password 

24 path. If you have otp data, they should be named as ``otpauth``. 

25 These are the *standard* field names. You can add any other field 

26 you want. 

27 

28 """ 

29 cap = Cap.IMPORT | Cap.EXPORT 

30 name = 'csv' 

31 himport = "pass import csv file.csv --cols 'url,login,,password'" 

32 writer = None 

33 

34 # Import method 

35 

36 def parse(self): 

37 """Parse Generic CSV file.""" 

38 self.file.readline() 

39 if ',' in self.cols: 

40 self.fieldnames = self.cols.split(',') 

41 else: 

42 raise FormatError("no columns to map to credential attributes.") 

43 super().parse() 

44 

45 # Export methods 

46 

47 def exist(self): 

48 """Nothing to do.""" 

49 return True 

50 

51 def clean(self, cmdclean, convert): 

52 """Clean data for export in CSV a file.""" 

53 super().clean(cmdclean, convert) 

54 fieldnames = set() 

55 for entry in self.data: 

56 path = entry.pop('path', '') 

57 entry['group'] = os.path.join(self.root, os.path.dirname(path)) 

58 entry['title'] = os.path.basename(path) 

59 fieldnames.update(set(entry.keys())) 

60 

61 if not self.all: 

62 fieldnames = self.keyslist 

63 for entry in self.data: 

64 for key in fieldnames: 

65 if key not in entry: 

66 entry[key] = '' 

67 self.writer = csv.DictWriter(self.file, 

68 fieldnames=sorted(fieldnames), 

69 restval='', 

70 extrasaction='raise') 

71 self.writer.writeheader() 

72 

73 def insert(self, entry): 

74 """Insert a password entry into a CSV file. 

75 

76 :param dict entry: The password entry to insert. 

77 

78 If ``all`` is true, all the entry values are printed. 

79 Otherwise, only the key present in ``keyslist`` are 

80 printed following the order from this list. The title, path, and group 

81 keys are ignored. 

82 

83 Binary attachment is not supported. 

84 

85 """ 

86 if self.all: 

87 self.writer.writerow(entry) 

88 else: 

89 res = {} 

90 for key in self.keyslist: 

91 res[key] = entry.get(key, '') 

92 self.writer.writerow(res) 

93 

94 # Context manager method 

95 

96 def open(self): 

97 """Create/Re-create CSV exported file.""" 

98 if self.action is Cap.IMPORT: 

99 super().open() 

100 else: 

101 if os.path.isfile(self.prefix): 

102 if self.force: 

103 self.file = open(self.prefix, 'w', encoding=self.encoding) 

104 else: 

105 raise PMError(f"{self.prefix} is already a file.") 

106 else: 

107 self.file = open(self.prefix, 'w', encoding=self.encoding) 

108 

109 

110register_managers(GenericCSV)