-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
741 lines (593 loc) · 26.1 KB
/
main.py
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
"""This module is used to run the code.
defining some "activites" reading local files and connecting to a server
It mainly works based on df comparison
"""
import json
import logging
import logging.config
import os.path
import time
from pathlib import Path
import pandas as pd
from ChurchToolsApi import ChurchToolsApi
import SNG_DEFAULTS
from SngFile import SngFile
logger = logging.getLogger(__name__)
ct_domain = os.getenv("CT_DOMAIN")
ct_token = os.getenv("CT_TOKEN")
if ct_domain is None or ct_token is None:
from secure.config import ct_domain, ct_token
logger.info(
"ct_domain or ct_token missing in env variables - using local config instead"
)
from secure import config
ct_domain = config.ct_domain
ct_token = config.ct_token
def parse_sng_from_directory(
directory: str, songbook_prefix: str = "", filenames: list[str] | None = None
) -> list[SngFile]:
"""Method which reads all SNG Files from a directory and adds missing default values if missing.
Params:
directory: directory to read from
songbook_prefix: which should be used for Songbook number - usually related to directory
filenames: optional list of filenames which should be covered - if none all from directory will be read
Returns:
list of SngFile items read from the directory
"""
if filenames is None:
filenames = []
logger.info("Parsing: %s", directory)
logger.info("With Prefix: %s", songbook_prefix)
result = []
directory_list = filter(
lambda x: x.endswith((".sng", ".SNG", ".Sng")), os.listdir(directory)
)
if len(filenames) > 0:
directory_list = filenames
for sng_filename in directory_list:
current_song = SngFile(directory + "/" + sng_filename, songbook_prefix)
if "Editor" not in current_song.header:
current_song.header["Editor"] = SNG_DEFAULTS.SngDefaultHeader["Editor"]
logger.info("Added missing Editor for: %s", sng_filename)
result.append(current_song)
return result
def validate_all_headers(df_to_change: pd.DataFrame, fix: bool = False) -> pd.Series:
"""Method to start validation for all headers.
1. Validate Title
2. Validate all Songbook Entries
3. Remove all Illegal headers
4. Psalm Backgrounds
5. Check that all required headers are present
Params:
df_to_change: Dataframe which should me used
fix: boolean if data should be fixed - so far only applies for remove illegal headers and songbook fixing
Returns:
boolean Series with True for all entries that have no issues
"""
logger.info("Starting validate_all_headers(%s)", fix)
# 1. Validate Title
logger.info("Starting validate_header_title(%s)", fix)
headers_valid = df_to_change["SngFile"].apply(
lambda x: x.validate_header_title(fix)
)
# 2. Validate Songbook Entries
logger.info("Starting validate_header_songbook(%s)", fix)
headers_valid &= df_to_change["SngFile"].apply(
lambda x: x.validate_header_songbook(fix)
)
# 3. Remove all Illegal headers
logger.info("Starting validate_headers_illegal_removed(%s)", fix)
headers_valid &= df_to_change["SngFile"].apply(
lambda x: x.validate_headers_illegal_removed(fix)
)
# 4. fix caps of CCLI entry if required
df_to_change["SngFile"].apply(lambda x: x.fix_header_ccli_caps())
# Set Background for all Psalm entries
psalms_select = df_to_change["SngFile"].apply(lambda x: x.is_psalm())
logger.info(
"Starting validate_header_background(%s) for %s psalms", fix, sum(psalms_select)
)
headers_valid &= df_to_change[psalms_select]["SngFile"].apply(
lambda x: x.validate_header_background(fix)
)
# 6. Check that all required headers are present
logger.info("Starting validate_headers()")
df_to_change["SngFile"].apply(lambda x: x.validate_headers())
return headers_valid
def read_songs_to_df(testing: bool = False) -> pd.DataFrame:
"""Default method which reads all known directories used at Evangelische Kirchengemeinde Baiersbronn.
requires all directories from SNG_DEFAULTS to be present
Arguments:
* testing: if SNG_DEFAULTS.KnownDirectory or "testData/" should be used
"""
songs_temp = []
for key, value in SNG_DEFAULTS.KnownFolderWithPrefix.items():
if testing:
dirname = "testData/" + key
if not Path(dirname).exists():
continue
else:
dirname = SNG_DEFAULTS.KnownDirectory + key
dirprefix = value
songs_temp.extend(
parse_sng_from_directory(directory=dirname, songbook_prefix=dirprefix)
)
result_df = pd.DataFrame(songs_temp, columns=["SngFile"])
result_df["filename"] = ""
result_df["path"] = ""
for index, value in result_df["SngFile"].items():
result_df.loc[(index, "filename")] = value.filename
result_df.loc[(index, "path")] = value.path
return result_df
def get_ct_songs_as_df(api: ChurchToolsApi) -> pd.DataFrame:
"""Helper function reading all songs into a df.
Params:
api: reference to a churchtools system
Returns:
Dataframe with all Songs from ChurchTools Instance
"""
songs = api.get_songs()
return pd.json_normalize(songs)
def generate_title_column(df_to_change: pd.DataFrame) -> None:
"""Method used to generate the 'Title' column for all items in a df based on the headers.
Params:
df_to_change: Dataframe which should be used
"""
for index, value in df_to_change["SngFile"].items():
if "Title" in value.header:
df_to_change.loc[(index, "Title")] = value.header["Title"]
else:
df_to_change.loc[(index, "Title")] = None
logger.info("Song without a Title in Header: %s", value.filename)
def generate_songbook_column(df_to_change: pd.DataFrame) -> pd.DataFrame:
"""Method used to generate the 'Songbook' and 'ChurchSongID' columns on all items in a df based on the headers.
Currently works inplace, but also returns the df reference
Params:
df_to_change: Dataframe which should me used
Returns:
df with Songbook and ChurchSongID columns
"""
df_to_change["Songbook"] = df_to_change["SngFile"].apply(
lambda x: x.header.get("Songbook", None)
)
df_to_change["ChurchSongID"] = df_to_change["SngFile"].apply(
lambda x: x.header.get("ChurchSongID", None)
)
return df_to_change
def generate_background_image_column(df_to_change: pd.DataFrame) -> None:
"""Method used to generate the 'BackgroundImage' column for all items in a df based on the headers.
Params:
df_to_change: Dataframe which should me used
"""
for index, value in df_to_change["SngFile"].items():
if "BackgroundImage" in value.header:
df_to_change.loc[(index, "BackgroundImage")] = value.header[
"BackgroundImage"
]
def generate_ct_compare_columns(df_sng: pd.DataFrame) -> None:
"""Method used to generate the "id", 'name', 'category.name' for a local SNG Dataframe.
in order to match columns used in ChurchTools Dataframe
Params:
df_sng: Dataframe generated from SNG Files which which should me used
"""
df_sng["id"] = df_sng["SngFile"].apply(lambda x: x.get_id())
df_sng["name"] = df_sng["filename"].apply(lambda x: x[:-4])
df_sng["category.name"] = df_sng["SngFile"].apply(lambda x: x.path.name)
def clean_all_songs(df_sng: pd.DataFrame) -> pd.DataFrame:
"""Helper function which runs cleaning methods for sng files on a dataframe.
Arguments:
df_sng: Dataframe to work on - must have SngFile instances as attribute column
Returns:
a copy of the original dataframe with cleaning improvements applied
"""
df_result = df_sng.copy()
logger.info("starting validate_verse_order_coverage() with fix")
df_result["SngFile"].apply(lambda x: x.validate_verse_order_coverage(fix=True))
logger.info("starting fix_intro_slide()")
df_result["SngFile"].apply(lambda x: x.fix_intro_slide())
# Fixing without auto moving to end because sometimes on purpose, and cases might be
logger.info("starting validate_stop_verseorder(fix=True, should_be_at_end=False)")
df_result["SngFile"].apply(
lambda x: x.validate_stop_verseorder(fix=True, should_be_at_end=False)
)
# Logging cases that are not at end ...
# logger.info('starting validate_stop_verseorder(fix=False, should_be_at_end=True)')
# df_sng['SngFile'].apply(lambda x: x.validate_stop_verseorder(fix=False, should_be_at_end=True))
logger.info("starting validate_verse_numbers() with fix")
df_result["SngFile"].apply(lambda x: x.validate_verse_numbers(fix=True))
logger.info("starting validate_content_slides_number_of_lines() with fix")
df_result["SngFile"].apply(
lambda x: x.validate_content_slides_number_of_lines(fix=True)
)
validate_all_headers(df_result, True)
return df_result
def write_df_to_file(df_sng: pd.DataFrame, target_dir: str | None = None) -> None:
"""Write files to either it's original main directory or use a separate dir.
Params:
target_dir: e.g. './output' to write output into a separate folder
"""
if target_dir:
target_path = Path(target_dir)
logger.info("starting write_path_change(%s)", target_path)
df_sng["SngFile"].apply(lambda x: x.write_path_change(target_path))
logger.info("starting write_file()")
df_sng["SngFile"].apply(lambda x: x.write_file())
def check_ct_song_categories_exist_as_folder(
ct_song_categories: list[str], directory: Path, fix: bool = False
) -> set[str] | None:
"""Method which check whether Song Categories of ChurchTools exist in the specified folder.
Params:
ct_song_categories: List of all ChurchTools Song categories
directory: location of the SNG file collection to check for subfolders
fix: if missing folders for song categories should be created
Returns:
None if all exist, otherwise set of folder names that are missing
"""
logger.debug("checking categories %s in %s", ct_song_categories, directory)
categories_in_ct = set(ct_song_categories)
categories_in_directory = {folder.name for folder in directory.iterdir()}
missing_directories = categories_in_ct - categories_in_directory
if len(missing_directories) == 0:
return None
if fix:
for folder in missing_directories:
directory.mkdir(folder)
return check_ct_song_categories_exist_as_folder(
ct_song_categories=ct_song_categories, directory=directory, fix=False
)
logger.warning("Missing CT category %s in %s", missing_directories, directory)
return missing_directories
def validate_ct_songs_exist_locally_by_name_and_category(
df_ct: pd.DataFrame, df_sng: pd.DataFrame
) -> pd.DataFrame:
"""Function which checks that all song loaded from ChurchTools as DataFrame do exist locally.
Uses Name and Category to match them - does not compare IDs !
And logs warning for all elements that can not be mapped
Can be used to identify changes in ChurchTools that were not updated locally
Params:
df_ct: DataFrame with all columns from a JSON response getting all songs from CT
df_sng: DataFrame with all local SNG files with headings matching CT Dataframe
Returns:
reference to merged Dataframe
"""
generate_ct_compare_columns(df_sng)
logger.info("validate_ct_songs_exist_locally_by_name_and_category()")
df_ct_join_name = df_sng.merge(
df_ct, on=["name", "category.name"], how="right", indicator=True
)
issues = df_ct_join_name[df_ct_join_name["_merge"] != "both"].sort_values(
by=["category.name", "name"]
)
for issue in issues[["name", "category.name", "id_y"]].iterrows():
logger.warning(
"Song (%s) in category (%s) exists as ChurchTools ID=%s but not matched locally",
issue[1]["name"],
issue[1]["category.name"],
issue[1]["id_y"],
)
return df_ct_join_name
def validate_ct_songs_exist_locally_by_id(
df_ct: pd.DataFrame, df_sng: pd.DataFrame
) -> pd.DataFrame:
"""Function which checks that all song loaded from ChurchTools as DataFrame do exist locally.
Uses only ID to match them - does not compare name or cateogry !
And logs warning for all elements that can not be mapped
Can be used to identify changes in ChurchTools that were not updated locally
Params:
df_ct: DataFrame with all columns from a JSON response getting all songs from CT
df_sng: DataFrame with all local SNG files with headings matching CT Dataframe
Returns:
reference to merged Dataframe
"""
# prep df id, category and name columns
generate_ct_compare_columns(df_sng)
logger.info("validate_ct_songs_exist_locally_by_id()")
df_ct_join_id = df_sng.merge(df_ct, on=["id"], how="right", indicator=True)
issues = df_ct_join_id[df_ct_join_id["_merge"] != "both"].sort_values(
by=["category.name_y", "name_y"]
)
for issue in issues[["name_y", "category.name_y", "id"]].iterrows():
logger.warning(
"Song (%s) in category (%s) exists with ChurchTools ID=%s online but ID not matched locally",
issue[1]["name_y"],
issue[1]["category.name_y"],
issue[1]["id"],
)
return df_ct_join_id
def add_id_to_local_song_if_available_in_ct(
df_sng: pd.DataFrame, df_ct: pd.DataFrame
) -> None:
"""Helper function which write the ID of into each SNG file that.
* does not have a valid ChurchTools Song ID
* AND does have a match using name and category comparison with ChurchTools Dataframe
Params:
df_sng: All local songs to check - a copy i used for processing ...
df_ct: All known songs from ChurchTools
"""
logger.info("Starting add_id_to_local_song_if_available_in_ct()")
logger.critical(
"This function might destroy your data in case a songname exists twice in one songbook #13"
)
# TODO (bensteUEM): Extend functionality of add_id_to_local_song_if_available_in_ct()
# https://github.com/bensteUEM/SongBeamerQS/issues/13
compare_by_id_df = validate_ct_songs_exist_locally_by_id(df_ct, df_sng)
# Part used to overwrite local IDs with CT name_cat in case it exists in CT
ct_missing_by_id_df = compare_by_id_df[
compare_by_id_df["_merge"] == "right_only"
].copy()
ct_missing_by_id_df = ct_missing_by_id_df.drop(
["name_x", "category.name_x", "_merge"], axis=1
)
ct_missing_by_id_df = ct_missing_by_id_df.rename(
columns={"name_y": "name", "category.name_y": "category.name"}
)
overwrite_id_by_name_cat = validate_ct_songs_exist_locally_by_name_and_category(
ct_missing_by_id_df, df_sng
)
for _index, row in overwrite_id_by_name_cat.iterrows():
if isinstance(row["SngFile_x"], SngFile):
row["SngFile_x"].set_id(row["id_y"])
logger.debug(
"Prepare overwrite id %s for song %s with new id %s",
row["id_y"],
row["filename_x"],
row["id_y"],
)
else:
logger.warning(
'CT song ID= %s from (%s) with title "%s" not found locally',
row["id_y"],
row["category.name"],
row["name"],
)
missing_files = overwrite_id_by_name_cat["SngFile_x"].apply(
lambda x: not isinstance(x, SngFile)
)
overwrite_id_by_name_cat[~missing_files]["SngFile_x"].apply(
lambda x: x.write_file()
)
def download_missing_online_songs(
df_sng: pd.DataFrame, df_ct: pd.DataFrame, ct_api_reference: ChurchToolsApi
) -> bool:
"""Function which will check which songs are missing (by ID) and tries to download them to the respective folders.
It is highly recommended to execute add_id_to_local_song_if_available_in_ct() and
upload_new_local_songs_and_generate_ct_id() before in order to try to match all songs local and avoid duplicates
Params:
df_sng: DataFrame with all local files
df_ct: DataFrame with all online files
ct_api_reference: direct access to ChurchTools API instance
Returns:
Success message
"""
compare = validate_ct_songs_exist_locally_by_id(df_ct, df_sng)
song_path = compare[compare["path"].notna()].iloc[0]["path"]
collection_path = song_path.parent
ids = compare[compare["SngFile"].apply(lambda x: not isinstance(x, SngFile))]["id"]
is_successful = True
for song_id in ids:
song = ct_api_reference.get_songs(song_id=song_id)[0]
logger.debug(
'Downloading CT song id=%s "%s" (%s)',
song_id,
song["name"],
song["category"],
)
default_arrangement_id = next(
item["id"] for item in song["arrangements"] if item["isDefault"] is True
)
category_name = song["category"]["name"]
file_path_in_collection = Path(f"{collection_path}/{category_name}")
filename = f"{song['name']}.sng"
if Path.exists(Path("{file_path_in_collection}/{filename}")):
logger.warning(
"Local file %s from CT ID %s does already exist - try automatch instead!",
filename,
song_id,
)
is_successful &= False
continue
result = ct_api_reference.file_download(
filename=filename,
domain_type="song_arrangement",
domain_identifier=default_arrangement_id,
target_path=str(file_path_in_collection),
)
if result:
logger.debug(
"Downloaded %s into %s from CT IT %s",
filename,
file_path_in_collection,
song_id,
)
else:
logger.debug(
"Failed to download %s into %s from CT IT %s",
filename,
file_path_in_collection,
song_id,
)
is_successful &= result
return is_successful
def upload_new_local_songs_and_generate_ct_id(
df_sng: pd.DataFrame, df_ct: pd.DataFrame, default_tag_id: int = 52
) -> None:
"""Helper Function which creates new ChurchTools Songs for all SNG Files from dataframe which don't have a song ID.
Iterates through all songs in df_sng that don't match
New version of the SNG file including the newly created id is overwritten locally
Param:
df_sng: Pandas DataFrame with SNG objects that should be checked against
df_ct: Pandas DataFrame with Information retrieved about all ChurchTools Songs
default_tag_id: default ID used to tag new songs - depends on instance of churchtools used !
"""
generate_ct_compare_columns(df_sng)
to_upload = df_sng.merge(df_ct, on=["id"], how="left", indicator=True)
to_upload = to_upload[to_upload["_merge"] == "left_only"]
api = ChurchToolsApi(domain=ct_domain, ct_token=ct_token)
song_category_dict = api.get_song_category_map()
for _index, row in to_upload.iterrows():
title = row["filename"][:-4]
category_id = song_category_dict[row["category.name_x"]]
author1 = row["SngFile"].header.get("Author", "").split(", ")
author2 = row["SngFile"].header.get("Melody", "").split(", ")
authors = list(set(author1) | set(author2))
authors = ", ".join(filter(None, authors))
ccli = row["SngFile"].header.get("CCLI", "")
copy = row["SngFile"].header.get("(c)", "")
logger.info(
"Uploading Song '%s' with Category ID '%s' from '%s' with (C) from '%s' and CCLI '%s'",
title,
category_id,
authors,
copy,
ccli,
)
song_id = api.create_song(
title=title,
songcategory_id=category_id,
author=authors,
copyright=copy,
ccli=ccli,
)
logger.debug("Created new Song with ID '%s'", song_id)
api.add_song_tag(song_id=song_id, song_tag_id=default_tag_id)
row["SngFile"].set_id(song_id)
row["SngFile"].write_file()
song = api.get_songs(song_id=song_id)[0]
api.file_upload(
str(row["path"] / row["filename"]),
domain_type="song_arrangement",
domain_identifier=next(
i["id"] for i in song["arrangements"] if i["isDefault"] is True
),
overwrite=False,
)
def upload_local_songs_by_id(df_sng: pd.DataFrame, df_ct: pd.DataFrame) -> None:
"""Helper function that overwrites the SNG file of the default arrangement in ChurchTools with same song id.
Params:
df_sng: the local song library as dataframe
df_ct: the remote song library as dataframe
"""
generate_ct_compare_columns(df_sng)
to_upload = df_sng.merge(df_ct, on=["id"], how="left", indicator=True)
api = ChurchToolsApi(domain=ct_domain, ct_token=ct_token)
to_upload["arrangement_id"] = to_upload["arrangements"].apply(
lambda x: next(i["id"] for i in x if i["isDefault"])
)
progress = 0
target = len(to_upload)
for progress, data in enumerate(to_upload.iterrows()):
_index, row = data
api.file_upload(
str(row["path"] / row["filename"]),
domain_type="song_arrangement",
domain_identifier=row["arrangement_id"],
overwrite=True,
)
if progress % 50 == 0:
logger.info("Finished upload %s of %s - sleep 15", progress, target)
time.sleep(15)
logger.info(
"upload_local_songs_by_id - will overwrite all CT SNG default arrangement files with known ID"
)
def apply_ct_song_sng_count_qs_tag(
api: ChurchToolsApi, song: dict, tags_by_name: dict
) -> None:
"""Helper function which adds tags to songs in case sng attachment counts mismatches expectations.
Requires respective tags to be present - can be ensured using prepare_required_song_tags()
Args:
api: instance connected to any churchtools instance
song: churchtools song as dict
tags_by_name: dictionary referencing tag ids by name
"""
for arrangement in song["arrangements"]:
sngs = [True for file in arrangement["files"] if ".sng" in file["name"]]
number_of_sngs = len(sngs)
if number_of_sngs == 1:
api.remove_song_tag(
song_id=song["id"], song_tag_id=tags_by_name["QS: missing sng"]
)
api.remove_song_tag(
song_id=song["id"], song_tag_id=tags_by_name["QS: too many sng"]
)
elif number_of_sngs == 0:
api.add_song_tag(
song_id=song["id"], song_tag_id=tags_by_name["QS: missing sng"]
)
elif number_of_sngs > 1:
api.add_song_tag(
song_id=song["id"], song_tag_id=tags_by_name["QS: too many sng"]
)
def prepare_required_song_tags(api: ChurchToolsApi) -> dict:
"""Helper which retrieves all song tags, checks that all required ones exist and returns dict of values.
Returns:
dict of name:id pairs for song tags
"""
tags = api.get_tags(type="songs")
tags_by_name = {tag["name"]: tag["id"] for tag in tags}
# missing sng
if "QS: missing sng" not in tags_by_name:
pass
# TODO@Benedict: implement create_tag
# https://github.com/bensteUEM/ChurchToolsAPI/issues/92
# name = "QS: missing sng"
# tag_id = api.create_tag(name=name,type="songs")
# tags_by_name[name]=tag_id
# logger.info("created %s with ID=%s on instance because song tag did not exist", name, tag_id)
# too many sng
if "QS: too many sng" not in tags_by_name:
pass
# TODO@Benedict: implement create_tag
# https://github.com/bensteUEM/ChurchToolsAPI/issues/92
# name = "QS: too many sng"
# tag_id = api.create_tag(name=name,type="songs")
# tags_by_name[name]=tag_id
# logger.info("created %s with ID=%s on instance because song tag did not exist", name, tag_id)
return tags_by_name
def validate_ct_song_sng_count(api: ChurchToolsApi) -> None:
"""Check that all arrangements from ChurchTools songs have exactly 1 sng attachment.
If there is no sng file attachment the song arrangement is incomplete and should be tagged with a "missing SNG" tag.
If there is more than one sng attachment agenda downloads might retrieve the wrong one therefore only should be tagged with "too many SNG" tag.
Arguments:
api: instance connected to any churchtools instance
"""
tags_by_name = prepare_required_song_tags(api=api)
songs = api.get_songs()
len_songs = len(songs)
for song_count, song in enumerate(songs):
apply_ct_song_sng_count_qs_tag(
api=api,
song=song,
tags_by_name=tags_by_name,
)
if song_count % 25 == 0:
# avoid Too many requests. Rate Limit Exceeded.
logger.debug("sleep 1 second after %s / %s", song_count, len_songs)
time.sleep(1)
if __name__ == "__main__":
config_file = Path("logging_config.json")
with config_file.open(encoding="utf-8") as f_in:
logging_config = json.load(f_in)
logging.config.dictConfig(config=logging_config)
logger.info("Excecuting Main RUN")
songs_temp = []
df_sng = read_songs_to_df()
df_sng = clean_all_songs(df_sng=df_sng)
write_df_to_file(df_sng)
api = ChurchToolsApi(domain=ct_domain, ct_token=ct_token)
validate_ct_song_sng_count(api)
# Match all SongIDs from CT to local songs where missing
df_ct = get_ct_songs_as_df(api)
add_id_to_local_song_if_available_in_ct(df_sng, df_ct)
# Upload all songs into CT that are new
df_ct = get_ct_songs_as_df(api)
upload_new_local_songs_and_generate_ct_id(df_sng, df_ct)
# To be safe - re-read all data sources and upload
df_sng = read_songs_to_df()
df_ct = get_ct_songs_as_df(api)
download_missing_online_songs(df_sng, df_ct, api)
"""
df_sng = read_baiersbronn_songs_to_df()
df_ct = get_ct_songs_as_df()
upload_local_songs_by_id(df_sng, df_ct)
"""
logger.info("Main Method finished")