-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpbiviz_distrib.py
More file actions
168 lines (152 loc) · 8.93 KB
/
pbiviz_distrib.py
File metadata and controls
168 lines (152 loc) · 8.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import json, csv, zipfile, subprocess, os, sys, logging, argparse
from datetime import datetime
logging.basicConfig(level=logging.INFO,
format="%(levelname)s: %(message)s")
class Pbiviz_Packager:
def __init__(self, code_folder, output_folder, csvpath=None) -> None:
self.code_folder = code_folder
self.csvpath = csvpath
self.output_folder = output_folder
if csvpath and not os.path.exists(csvpath):
raise RuntimeError("Specified CSV file not found")
def get_git_commit(self):
# get most recent commit using git command
result = subprocess.run(
['git', 'rev-parse', '--short=6', 'HEAD'], capture_output=True, text=True)
commit_hash = result.stdout.strip("\n")
return commit_hash
def calculate_output_details(self):
'''Populates necessary instance variables, comprising version number and the filename that
we expect pbiviz package will create for us based on this (we can't control it!)'''
timestamp = datetime.now().strftime('%Y%m%d%H%M')
git_commit_hash = self.get_git_commit()
updated_version_number = f"0.0.0.{git_commit_hash}_{timestamp}"
self.version_number = updated_version_number
pbiviz_json_path = os.path.join(self.code_folder, "pbiviz.json")
with open(pbiviz_json_path) as file:
pbiviz_json = json.load(file)
guid = pbiviz_json['visual']['guid']
expected_output_filename = f"{guid}.{self.version_number}.pbiviz"
self.expected_output_file = os.path.join(self.code_folder, 'dist', expected_output_filename)
self.expected_output_json = f"{guid}.pbiviz.json"
def update_version_in_json(self):
'''Replaces the version in the pbiviz.json file with a string based on the current date and git commit ref'''
pbiviz_json_path = os.path.join(self.code_folder, "pbiviz.json")
with open(pbiviz_json_path) as file:
pbiviz_json = json.load(file)
# Update version number
pbiviz_json['visual']['version'] = self.version_number
# write out json
with open(pbiviz_json_path, 'w') as file:
json.dump(pbiviz_json, file, indent=4)
def package_pbiviz(self):
'''Calls pbiviz package to actually package the visual. It will be written to the dist subfolder of the code
folder and will have a filename based on the GUID and version from pbiviz.json.'''
self.update_version_in_json()
# Get path to pbiviz powershell script
result = subprocess.run(['powershell.exe', '-Command', '(Get-Command pbiviz).path'], capture_output=True, text=True)
pbiviz_path = result.stdout.strip("\n")
# Call pbiviz package command
subprocess.run(["powershell.exe",
pbiviz_path, "package"]) # check=True if want to error handle
return os.path.exists(self.expected_output_file)
def create_registered_copies(self):
'''Creates copies of the visual that has been output by pbiviz package. One copy will be created for each
row of the CSV file. Within each, the values {{VISUAL_ID}} and {{EXPIRY_DATE}} will be substituted by
the values from the CSV, and the output filename will be named according to the ORG_NAME from the CSV'''
copies = self.get_reg_details()
out_folder = self.output_folder
os.makedirs(out_folder, exist_ok=True)
logging.debug(f"Will now create the following copies: \n{copies}")
for id, org_name, build_expiry in copies:
out_name = os.path.join(out_folder, f"OS_Maps_Visual_{org_name}.pbiviz")
replacements_to_make = [
("{{VISUAL_ID}}", id),
("{{EXPIRY_DATE}}", build_expiry),
("{{APP_INSIGHTS}}", os.environ("APP_INSIGHTS"))
]
self.replace_id_placeholders(
self.expected_output_file,
out_name,
replacements_to_make
#,self.expected_output_json
)
def get_reg_details(self):
''' Parses the input csv file to get details of the copies of the visual that should be created.
If there is no input csv, then creates a single copy with the placeholders unmodified and the
file named Ordnance Survey '''
items = []
if not self.csvpath:
# if called with no CSV, then do not actually replace the placeholder strings in the built
# visual, just copy it to a new file with Ordnance Survey in the filename
return [
('{{VISUAL_ID}}', 'Ordnance Survey', '{{EXPIRY_DATE}}')
]
with open(self.csvpath) as licencefile:
reader = csv.DictReader(licencefile)
for row in reader:
items.append((row['visual_id'], row['org_name'], row['expiry']))
return items
def replace_id_placeholders(self, pbiviz_zip_input, pbiviz_zip_output, replacements_to_make, filename_in_zip=None):
'''For a given input zip file, for every file within the zip or a specific single file within it, replaces
a number of strings with substitute values, creating a new output zip file.'''
with zipfile.ZipFile(pbiviz_zip_input, 'r') as zip_read:
with zipfile.ZipFile(pbiviz_zip_output, 'w') as zip_write:
replaced = 0
for item in zip_read.infolist():
if not replaced==len(replacements_to_make) and (filename_in_zip==None or item.filename == filename_in_zip):
# Read the content of the target file
with zip_read.open(item) as file:
content = file.read().decode('utf-8')
# Replace the string
for id_placeholder, id_value in replacements_to_make:
if id_placeholder in content:
content = content.replace(id_placeholder, id_value, 1)
logging.debug(f"Replaced placeholder in file {item.filename}")
replaced += 1
elif item.filename == filename_in_zip:
raise RuntimeError("cannot find the requested filename in zip")
else:
logging.debug(f"Placeholder {id_placeholder} not found in {item.filename}")
# Write the modified content to the new zip file
zip_write.writestr(item, content)
else:
# Copy other files without modification
logging.debug(f"File copied directly as replacment already done: {item.filename}")
zip_write.writestr(item, zip_read.read(item.filename))
if not replaced == len(replacements_to_make):
os.remove(pbiviz_zip_output)
logging.error(
f"Cannot find some of the placeholder strings, the output file has not been created")
else:
logging.info(
f"Output file {pbiviz_zip_output} created with visual_id {replacements_to_make[0][1]} and expiry date {replacements_to_make[1][1]}"
)
def RunBuild(self):
self.calculate_output_details()
self.update_version_in_json()
build_success = self.package_pbiviz()
if(build_success):
self.create_registered_copies()
def main(out_folder, input_csv):
#csvfile = r"C:\Documents_Local\Projects_Local\repos\osmaps-powerbi-api\OS_PowerBI_API\database\visual_ids.csv"
packager = Pbiviz_Packager(
code_folder=os.path.abspath('.') ,
output_folder=out_folder,
csvpath=input_csv)
packager.RunBuild()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
"Package the PBI visual, giving it a version number which includes the git commit hash and build time. "+
"Optionally create multiple copies of the output file which have different values of the VISUAL_ID "+
"internal property, with filename corresponding to the organisation to whom that ID is "+
"allocated.")
parser.add_argument("output_folder", type=str, help="Path to the destination/output folder")
parser.add_argument("--input_csv", type=str, default= None, help=
"Path to the input CSV file. Must contain columns named visual_id, org_name and expiry. One copy of the "+
"visual will be output for each row of the CSV. This CSV should be the same one that is used "+
"by the PowerBI API to check authorisation!! If not provided then a single copy of the visual "+
"will be created with default visual id. ")
#parser.add_argument("expiry_date", type=lambda d: datetime.strptime(d, '%Y-%m-%d'))
args = parser.parse_args()
main(args.output_folder, args.input_csv)#, args.expiry_date)