Hallo,
eine Frage an die Allgemeinheit: Mal unabhängig ob die Klasse Sinn macht, wäre dass dann jetzt so wie sie da steht nicht etwas für `@dataclass` ?
Grüße
Dennis
PyCopy Erstlingswerk
- __blackjack__
- User
- Beiträge: 13457
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Dennis89: Oder `namedtuple`.
Unabhängig davon scheint mir das nicht wirklich sinnvoll das alles in ein Objekt zu stecken, beziehungsweise das Objekt das `parse_args()` liefert, noch mal in ein anderes Objekt umzukopieren, das auch kein Verhalten hat. Und dazu dann auch noch das ursprüngliche `args` da mit rein zu stecken.
Unabhängig davon scheint mir das nicht wirklich sinnvoll das alles in ein Objekt zu stecken, beziehungsweise das Objekt das `parse_args()` liefert, noch mal in ein anderes Objekt umzukopieren, das auch kein Verhalten hat. Und dazu dann auch noch das ursprüngliche `args` da mit rein zu stecken.
Code: Alles auswählen
- (void)countSheep {
unsigned int sheep = 0;
while ( ! [self isAsleep]) { ++sheep; }
}
meine Überlegung dabei war: wie bekomme ich die args - die ja main() bzw die aufgerufenen Methoden auch benutzen müssen) sonst raus aus der parameter() Methode? Das return ist ja schon belegt mit params__blackjack__ hat geschrieben: ↑Samstag 28. September 2024, 23:24 @Dennis89: Oder `namedtuple`.
Unabhängig davon scheint mir das nicht wirklich sinnvoll das alles in ein Objekt zu stecken, beziehungsweise das Objekt das `parse_args()` liefert, noch mal in ein anderes Objekt umzukopieren, das auch kein Verhalten hat. Und dazu dann auch noch das ursprüngliche `args` da mit rein zu stecken.
Außerdem muß es unten heißen:
war gestern spät. habe die falsche, vorletzte Quelle gepostet.
Code: Alles auswählen
for source,target in copy_items:
done_list = copy_list(source, target, copy_items, done_list, parms)
`recursive =args.notrecursive` macht logisch immer noch keinen Sinn. extfolder sollte den Wert None haben, wenn er nicht gesetzt ist. Path.cwd liefert bereits ein Path-Objekt, das nochmal in eines zu verwandeln ist unnötig. Wenn root nur das aktuelle Verzeichnis enthält ist es überflüssig, weil jeder relative Pfad sich auf das aktuelle Arbeitsverzeichnis bezieht.
path.name ist niemals leer, so dass der zweite Zweig in extend_path_by_timestamp nie benutzt wird. Hier sollte man besser with_name benutzen.
In copy_list benutzt Du noch os-Funktionen, obwohl Du ja eigentlich pathlib benutzt.
yn und rc sind keine sprechenden Variablenname.
Obwohl Du Path benutzt, fängst Du in dieser Funktion wieder mit Stringgefummel an. member wäre schon ein Path-Objekt, wenn Du source.iterdir statt os.listdir verwendet hättest.
Der Rückgabewert von copyfile ist nie etwas unwahres, die if-Abfrage daher unnötig.
path.name ist niemals leer, so dass der zweite Zweig in extend_path_by_timestamp nie benutzt wird. Hier sollte man besser with_name benutzen.
In copy_list benutzt Du noch os-Funktionen, obwohl Du ja eigentlich pathlib benutzt.
yn und rc sind keine sprechenden Variablenname.
Obwohl Du Path benutzt, fängst Du in dieser Funktion wieder mit Stringgefummel an. member wäre schon ein Path-Objekt, wenn Du source.iterdir statt os.listdir verwendet hättest.
Der Rückgabewert von copyfile ist nie etwas unwahres, die if-Abfrage daher unnötig.
- __blackjack__
- User
- Beiträge: 13457
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@alfware17: Wenn dann den Kram der aus `args` benutzt wird auch auf Attribute in den Rückgabewert umpacken, aber das nicht auch noch verschachteln. Ich finde es aber sowieso nicht wirklich sinnvoll einen Haufen ehemals unübersichtliche globale Variablen einfach 1:1 in eine Klasse zu verschieben, denn letztlich ändert das ja nichts am Problem wenn man die dann einfach alle in einem genau so unübersichtlichen Haufen durchreicht.
Edit: Das mit dem `wait` wurde offenbar nicht ausprobiert, denn das funktioniert nicht mit der Nachfrage. Die liefert in dem Fall eine Methode die dann versucht wird wie eine Zeichenkette zu benutzen.
Edit: Das mit dem `wait` wurde offenbar nicht ausprobiert, denn das funktioniert nicht mit der Nachfrage. Die liefert in dem Fall eine Methode die dann versucht wird wie eine Zeichenkette zu benutzen.
Code: Alles auswählen
- (void)countSheep {
unsigned int sheep = 0;
while ( ! [self isAsleep]) { ++sheep; }
}
- __blackjack__
- User
- Beiträge: 13457
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Noch was: Von `find()` würde ich Abstand nehmen, denn das liefert ja auch einen gültigen Index wenn nichts gefunden wird, und dann sollte -1 besser das sein was man da wirklich weiterbenutzen möchte ohne, dass das zu Problemen führt die man erst später bemerkt. Wenn man einfach nur testen will ob eine Zeichenkette in einer anderen *vorkommt* ist ``in`` sowieso die bessere, offensichtlichere Wahl, wobei das bei ".tar" eher nicht das ist was man wissen möchte ob das *irgendwo* vorkommt, sondern ob das eine Dateinamenserweiterung ist. Auch da wäre dann wieder `Path` sinnvoller um sich die Erweiterung(en) von einem Dateipfad geben zu lassen.
Code: Alles auswählen
- (void)countSheep {
unsigned int sheep = 0;
while ( ! [self isAsleep]) { ++sheep; }
}
Hallo __blackjack__ , ja die parms-dataclass habe ich noch mal umgeschrieben und nun sind nur 4 Attribute geblieben - source und target, weil die nun wirklich gegenüber den Parametern umgebogen werden können, denkt man vor allem an source ja/nein dann target ja/nein und umgekehrt sowie den Fall daß source . (Punkt) ist, dann soll target mit _home anfangen (standardmäßig mit Timestamp). Das war eigentlich der Grundfall meines Backup-Programmes. Timestamp und Filename auch. Und eigentlich werden die args und parms Sammlungen gar nicht so oft gebraucht.
Das wait funktioniert wohl - jedenfalls jetzt, ich hatte wohl gestern wirklich die vorletzte Source gepostet - das mit der Methodenadresse war mir auch aufgefallen erst.
Das "find" hat mir ChatGPT heimlich auch ausgebaut und so wie von dir vorgeschlagen mit in.
Hallo Sirius3, vielen Dank für die vielen detaillierten Hinweise.
- root ist raus, war ein Überbleibsel aus der Zeit wo ich ohne pathlib klarkommen mußte.
- os habe ich ersetzt, ich wußte gar nicht bzw hätte nicht gehofft, wie umfangreich pathlib ist und os damit eigentlich redundant. Kleiner Wermutstropfen: die listdir-Methode funktioniert ein wenig anders, es sind bei dem Membern die Pfade mit dran, die muß ich abschneiden, da ich die Namen einzeln und dann jeweils im source- und target- Ordner brauche. Aber ok, für andere Aufgaben zB shutils.copytree sieht das wieder ganz anders aus, das wollte ich aber bewußt nicht nehmen
-generell stehe ich mit dem Path noch ein wenig auf Kriegsfuß - speziell das Konvertieren zwischen String und Path ist manchmal zum Haareraufen, wenn die Spezialfälle leer, home (.) ins Spiel kommen und dann der Parameter vom CLI auch wirklich ein space ist und kein Punkt. Ich hoffe ich habe das jetzt konsistent hinbekommen - False bzw space solange wie möglich und dann Punkt wenn es um Pfade geht.
Ja das ganze erinnert mich an viele Jahre Verzweifeln an NUL usw in anderen Sprachen.
- die Sache mit dem notrecursive. Naja, es ist halt die Variablenbezeichnung, dummerweise soll das bei argparse so angezeigt werden daß der Nutzer das rekursiv ausknipsen soll. Im Programm funktioniert es so, vielleicht habe ich bei der Benennung auch einen Knoten im Kopf.
Gute Nachricht: Unter Linux läuft das Programm auch. In den Pfaden steht "Posix-Path" statt "Windows-Path" drin. Ehrlich gesagt habe ich die letzte Version auch unter Linux entwickelt/bearbeitet, dabei kamen noch ein paar Schlampereien zum Vorschein, mit False und Leer - Windows-Python ist da anscheinend toleranter und behandelt das lockerer, Linux bestand aber auf Typengenauigkeit. Auch nicht schlecht, so mußte ich es genau machen
Aktueller Quelltext:
Das wait funktioniert wohl - jedenfalls jetzt, ich hatte wohl gestern wirklich die vorletzte Source gepostet - das mit der Methodenadresse war mir auch aufgefallen erst.
Das "find" hat mir ChatGPT heimlich auch ausgebaut und so wie von dir vorgeschlagen mit in.
Hallo Sirius3, vielen Dank für die vielen detaillierten Hinweise.
- root ist raus, war ein Überbleibsel aus der Zeit wo ich ohne pathlib klarkommen mußte.
- os habe ich ersetzt, ich wußte gar nicht bzw hätte nicht gehofft, wie umfangreich pathlib ist und os damit eigentlich redundant. Kleiner Wermutstropfen: die listdir-Methode funktioniert ein wenig anders, es sind bei dem Membern die Pfade mit dran, die muß ich abschneiden, da ich die Namen einzeln und dann jeweils im source- und target- Ordner brauche. Aber ok, für andere Aufgaben zB shutils.copytree sieht das wieder ganz anders aus, das wollte ich aber bewußt nicht nehmen
-generell stehe ich mit dem Path noch ein wenig auf Kriegsfuß - speziell das Konvertieren zwischen String und Path ist manchmal zum Haareraufen, wenn die Spezialfälle leer, home (.) ins Spiel kommen und dann der Parameter vom CLI auch wirklich ein space ist und kein Punkt. Ich hoffe ich habe das jetzt konsistent hinbekommen - False bzw space solange wie möglich und dann Punkt wenn es um Pfade geht.
Ja das ganze erinnert mich an viele Jahre Verzweifeln an NUL usw in anderen Sprachen.
- die Sache mit dem notrecursive. Naja, es ist halt die Variablenbezeichnung, dummerweise soll das bei argparse so angezeigt werden daß der Nutzer das rekursiv ausknipsen soll. Im Programm funktioniert es so, vielleicht habe ich bei der Benennung auch einen Knoten im Kopf.
Gute Nachricht: Unter Linux läuft das Programm auch. In den Pfaden steht "Posix-Path" statt "Windows-Path" drin. Ehrlich gesagt habe ich die letzte Version auch unter Linux entwickelt/bearbeitet, dabei kamen noch ein paar Schlampereien zum Vorschein, mit False und Leer - Windows-Python ist da anscheinend toleranter und behandelt das lockerer, Linux bestand aber auf Typengenauigkeit. Auch nicht schlecht, so mußte ich es genau machen
Aktueller Quelltext:
Code: Alles auswählen
from dataclasses import dataclass
from pathlib import Path
import shutil, argparse
from datetime import datetime
@dataclass
class ParameterBundle:
source: Path
target: Path
folder_timestamp: str
input_file: Path
def parameter():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-f", "--file", help="Filename for PDS")
parser.add_argument("-p", "--prefix", help="Prefix added before target")
parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
parser.add_argument("-i", "--source", help="Single Source folder, may be .")
parser.add_argument("-o", "--target", help="Single Target folder")
parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default")
parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
parser.add_argument("-t", "--notimestamp", action="store_true", help="Do not Add Timestamp to folder (not advised!)")
parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
args = parser.parse_args()
if args.file and args.source:
parser.error("You cannot use --file and --source at the same time")
if not args.prefix and args.notimestamp:
parser.error("--notimestamp without --prefix not allowed.")
source = args.source
target = args.target
if args.source is None:
if args.target:
target = None
print(f"--target {args.target} ignored")
else:
target = args.target if args.target else args.source
input_file = args.file if args.file else Path("copylist.txt")
folder_timestamp = "" if args.notimestamp else f"_d{datetime.now():%Y%m%d_t%H%M%S}"
return args, ParameterBundle(source, target, folder_timestamp, input_file)
def Path_str(path):
if path is None or path == "":
return None
return Path(path)
def extend_path_by_prefix_timestamp(path, prefix, timestamp):
prefix = "" if prefix is None else prefix
if path.name:
new_path = path.parent.joinpath(prefix + path.name + timestamp)
else:
path_str = "" if Path(path) == Path(".") else str(path)
new_path = Path(prefix + path_str + timestamp)
return new_path
def create_copy_items(source_items, args, parms):
copy_items = []
extfolder = Path(".") if args.extfolder is None else Path_str(args.extfolder)
for source_item, target_item in source_items:
source_path = Path_str(source_item)
target_path = Path_str(target_item)
if source_path == Path("."):
target_new = Path("_home")
prefix = None
print(f"--prefix {args.prefix} ignored")
else:
target_new = target_path
prefix = args.prefix
target_new = extend_path_by_prefix_timestamp(target_new, prefix, parms.folder_timestamp)
copy_items.append((extfolder.joinpath(source_path), extfolder.joinpath(target_new)))
return copy_items
def read_source_file(path):
source_items = []
try:
with open(path, "r") as file:
for line in file:
source_path = Path(line.rstrip())
source_items.append((source_path, source_path))
except FileNotFoundError:
print(f"File Open Error: {path} not found.")
return source_items
def copy_list(source, target, copy_items, done_list, args, parms):
extfolder = Path(".") if args.extfolder is None else Path_str(args.extfolder)
prefix = "" if args.prefix is None else str(args.prefix)
if not source.exists():
print(f"{source} not found")
return done_list
members_voll = source.iterdir()
members = [member.name for member in members_voll]
if len(members) == 0:
print(f"No members found in {source}")
return done_list
if args.verbose:
print(f"\nSource: {source}\nTarget: {target}\nCopy following members: {', '.join(members)}")
else:
print(f"\nProcessing: {source}")
proceed = input("Proceed Y/(N) : ").upper() if args.wait else "Y"
if proceed != "Y":
return done_list
if not target.exists():
Path(target).mkdir(parents=True, exist_ok=True)
print(f"{target} created")
copied_count = 0
for member in members:
if member in prefix.split("/"):
continue
if args.excludetar and ".tar" in member:
continue
source_member = source.joinpath(member)
target_member = target.joinpath(member)
if source_member.is_dir():
if any(item[0] == source_member for item in copy_items + done_list):
continue
if not args.backupdouble and source == extfolder and member.startswith("_home"):
continue
if args.notrecursive:
done_list = copy_list(source_member, target_member, copy_items, done_list, args, parms)
# This was subdir and/or has already done. Northing else to copy
continue
shutil.copyfile(source_member, target_member)
if args.verbose:
print(f"{member} copied from {source.relative_to(extfolder)} to {target.relative_to(extfolder)}")
copied_count += 1
if copied_count > 0:
print(f"{copied_count} members copied")
if not args.homedouble:
done_list.append((source, None))
done_list.append((target, None))
return done_list
def main():
print("\npycopy start\n")
args, parms = parameter()
if parms.source:
source_items = [ (parms.source, parms.target) ]
else:
source_items = read_source_file(parms.input_file)
copy_items = create_copy_items(source_items, args, parms)
done_list = []
for source,target in copy_items:
done_list = copy_list(source, target, copy_items, done_list, args, parms)
print("\npycopy done\n")
if __name__ == "__main__":
main()
Schau mal, dass ist jetzt dein Code ohne die Klasse, sonst habe ich nichts geändert.
Findest du, dass die Klasse wirklich Sinn macht?
Grüße
Dennis
Findest du, dass die Klasse wirklich Sinn macht?
Code: Alles auswählen
from pathlib import Path
import shutil, argparse
from datetime import datetime
def parameter():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-f", "--file", help="Filename for PDS")
parser.add_argument("-p", "--prefix", help="Prefix added before target")
parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
parser.add_argument("-i", "--source", help="Single Source folder, may be .")
parser.add_argument("-o", "--target", help="Single Target folder")
parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default")
parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
parser.add_argument("-t", "--notimestamp", action="store_true",
help="Do not Add Timestamp to folder (not advised!)")
parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
args = parser.parse_args()
if args.file and args.source:
parser.error("You cannot use --file and --source at the same time")
if not args.prefix and args.notimestamp:
parser.error("--notimestamp without --prefix not allowed.")
source = args.source
target = args.target
if args.source is None:
if args.target:
target = None
print(f"--target {args.target} ignored")
else:
target = args.target if args.target else args.source
input_file = args.file if args.file else Path("copylist.txt")
folder_timestamp = "" if args.notimestamp else f"_d{datetime.now():%Y%m%d_t%H%M%S}"
return args, source, target, input_file, folder_timestamp
def Path_str(path):
if path is None or path == "":
return None
return Path(path)
def extend_path_by_prefix_timestamp(path, prefix, timestamp):
prefix = "" if prefix is None else prefix
if path.name:
new_path = path.parent.joinpath(prefix + path.name + timestamp)
else:
path_str = "" if Path(path) == Path(".") else str(path)
new_path = Path(prefix + path_str + timestamp)
return new_path
def create_copy_items(source_items, args, folder_timestamp):
copy_items = []
extfolder = Path(".") if args.extfolder is None else Path_str(args.extfolder)
for source_item, target_item in source_items:
source_path = Path_str(source_item)
target_path = Path_str(target_item)
if source_path == Path("."):
target_new = Path("_home")
prefix = None
print(f"--prefix {args.prefix} ignored")
else:
target_new = target_path
prefix = args.prefix
target_new = extend_path_by_prefix_timestamp(target_new, prefix, folder_timestamp)
copy_items.append((extfolder.joinpath(source_path), extfolder.joinpath(target_new)))
return copy_items
def read_source_file(path):
source_items = []
try:
with open(path, "r") as file:
for line in file:
source_path = Path(line.rstrip())
source_items.append((source_path, source_path))
except FileNotFoundError:
print(f"File Open Error: {path} not found.")
return source_items
def copy_list(source, target, copy_items, done_list, args):
extfolder = Path(".") if args.extfolder is None else Path_str(args.extfolder)
prefix = "" if args.prefix is None else str(args.prefix)
if not source.exists():
print(f"{source} not found")
return done_list
members_voll = source.iterdir()
members = [member.name for member in members_voll]
if len(members) == 0:
print(f"No members found in {source}")
return done_list
if args.verbose:
print(f"\nSource: {source}\nTarget: {target}\nCopy following members: {', '.join(members)}")
else:
print(f"\nProcessing: {source}")
proceed = input("Proceed Y/(N) : ").upper() if args.wait else "Y"
if proceed != "Y":
return done_list
if not target.exists():
Path(target).mkdir(parents=True, exist_ok=True)
print(f"{target} created")
copied_count = 0
for member in members:
if member in prefix.split("/"):
continue
if args.excludetar and ".tar" in member:
continue
source_member = source.joinpath(member)
target_member = target.joinpath(member)
if source_member.is_dir():
if any(item[0] == source_member for item in copy_items + done_list):
continue
if not args.backupdouble and source == extfolder and member.startswith("_home"):
continue
if args.notrecursive:
done_list = copy_list(source_member, target_member, copy_items, done_list, args)
# This was subdir and/or has already done. Northing else to copy
continue
shutil.copyfile(source_member, target_member)
if args.verbose:
print(f"{member} copied from {source.relative_to(extfolder)} to {target.relative_to(extfolder)}")
copied_count += 1
if copied_count > 0:
print(f"{copied_count} members copied")
if not args.homedouble:
done_list.append((source, None))
done_list.append((target, None))
return done_list
def main():
print("\npycopy start\n")
args, source, target, input_file, folder_timestamp = parameter()
if source:
source_items = [(source, target)]
else:
source_items = read_source_file(input_file)
copy_items = create_copy_items(source_items, args, folder_timestamp)
done_list = []
for source, target in copy_items:
done_list = copy_list(source, target, copy_items, done_list, args)
print("\npycopy done\n")
if __name__ == "__main__":
main()
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Die Attribute von ParameterBundle sind teilweise falsch typisiert. source und target sind Strings, keine Path-Objekte.
Funktionen sollten nach Tätigkeiten benannt sein, und komplett klein geschrieben werden, Path_str hält sich nicht daran.
Die Funktion an sich ist aber auch überflüssig, bzw. macht wohl an einigen Stellen im Zweifel etwas falsch, weil None-Werten nicht erwartet werden.
In `extend_path_by_prefix_timestamp` ist die Behandlung des Sonderfalls, dass path.name leer ist mit dem Sonderfall dass Path == "." ist, vermischt. Das kann man klarer schreiben:
Da aber in `create_copy_items` der Fall path="." ausgeschlossen ist, vereinfacht sich die Funktion so stark, dass es eigentlich gar keine eigene Funktion braucht.
In `create_copy_items` sollten source_item und target_item schon Path-Objekte sein. extfolder sollte dementsprechend schon als Path-Objekt übergeben werden und nicht erst innerhalb der Funktion umgewandelt werden, denn das ist etwas undurchsichtig. Von args wird dann nur prefix und von params nur folder_timestamp verwendet, so dass man beides auch direkt übergeben kann.
Für `copy_list` wird jetzt ja auch schon das richtige extfolder übergeben, so dass sich die Dopplung des Codes für die Umwandlung erübrigt.
Eine Liste, die übergeben wird, um sie dann zu ändern, sollte nicht wieder als Rückgabewert zurückgegeben werden. Das verwirrt nur, ob wirklich die selbe Liste verwendet wird?
Es ist komisch, dass Du erst die Path-Objekte aus source ihres Pfades beraubst, um sie dann wieder an source zu binden.
Dieses `if member in prefix` verstehe ich nicht. Welchen Einfluß sollte ein Präfix auf die members haben?
`continue` sollte man vermeiden, weil es den Programmablauf undurchsichtig macht, vor allem, wenn continue in tief verschachtelten if-Abfragen auftaucht.
Die Sache mit den copy_items verstehe ich auch nicht ganz. Es sollen explizit angegebene einzelne Dateien nicht sofort kopiert werden. Aber dann kann man sie einfach schon in der done_list vormerken.
Dieses unnötige None-Tuple in done_list gehört weg. Man sollte nicht einfach nur, weil irgendwo anders etwas anderes erwartet wird, eine komplizierte Datenstruktur erzeugen, sondern die Stelle, wo es die Probleme gibt, beheben. done_list wäre dann auch besser ein Set.
Und hier der Rest, entsprechend angepasst:
Funktionen sollten nach Tätigkeiten benannt sein, und komplett klein geschrieben werden, Path_str hält sich nicht daran.
Die Funktion an sich ist aber auch überflüssig, bzw. macht wohl an einigen Stellen im Zweifel etwas falsch, weil None-Werten nicht erwartet werden.
In `extend_path_by_prefix_timestamp` ist die Behandlung des Sonderfalls, dass path.name leer ist mit dem Sonderfall dass Path == "." ist, vermischt. Das kann man klarer schreiben:
Code: Alles auswählen
def extend_path_by_prefix_timestamp(path, prefix, timestamp):
if prefix is None:
prefix = ""
if path == Path("."):
new_path = Path(prefix + timestamp)
else:
new_path = path.with_name(prefix + path.name + timestamp)
return new_path
In `create_copy_items` sollten source_item und target_item schon Path-Objekte sein. extfolder sollte dementsprechend schon als Path-Objekt übergeben werden und nicht erst innerhalb der Funktion umgewandelt werden, denn das ist etwas undurchsichtig. Von args wird dann nur prefix und von params nur folder_timestamp verwendet, so dass man beides auch direkt übergeben kann.
Code: Alles auswählen
def create_copy_items(source_items, extfolder, prefix, folder_timestamp):
copy_items = []
if prefix is None:
prefix = ""
for source_path, target_path in source_items:
if source_path == Path("."):
if prefix:
print(f"--prefix {prefix} ignored")
target_new = Path(f"_home{folder_timestamp}")
else:
target_new = target_path.with_name(prefix + path.name + folder_timestamp)
copy_items.append((extfolder / source_path, extfolder / target_new))
return copy_items
Eine Liste, die übergeben wird, um sie dann zu ändern, sollte nicht wieder als Rückgabewert zurückgegeben werden. Das verwirrt nur, ob wirklich die selbe Liste verwendet wird?
Es ist komisch, dass Du erst die Path-Objekte aus source ihres Pfades beraubst, um sie dann wieder an source zu binden.
Dieses `if member in prefix` verstehe ich nicht. Welchen Einfluß sollte ein Präfix auf die members haben?
`continue` sollte man vermeiden, weil es den Programmablauf undurchsichtig macht, vor allem, wenn continue in tief verschachtelten if-Abfragen auftaucht.
Die Sache mit den copy_items verstehe ich auch nicht ganz. Es sollen explizit angegebene einzelne Dateien nicht sofort kopiert werden. Aber dann kann man sie einfach schon in der done_list vormerken.
Dieses unnötige None-Tuple in done_list gehört weg. Man sollte nicht einfach nur, weil irgendwo anders etwas anderes erwartet wird, eine komplizierte Datenstruktur erzeugen, sondern die Stelle, wo es die Probleme gibt, beheben. done_list wäre dann auch besser ein Set.
Code: Alles auswählen
def copy_list(source, target, pathes_done, extfolder, prefix, args):
if not source.exists():
print(f"{source} not found")
return
members = list(source.iterdir())
if not members:
print(f"No members found in {source}")
return
if args.verbose:
print(f"\nSource: {source}\nTarget: {target}\nCopy following members: {', '.join(m.name for m in members)}")
else:
print(f"\nProcessing: {source}")
proceed = input("Proceed Y/(N) : ").upper() if args.wait else "Y"
if proceed != "Y":
return
if not target.exists():
target.mkdir(parents=True, exist_ok=True)
print(f"{target} created")
copied_count = 0
for source_member in members:
target_member = target / source_member.name
if args.excludetar and ".tar" in source_member.suffixes:
pass
elif source_member.is_dir():
if args.notrecursive:
pass
elif source_member in pathes_done:
pass
elif not args.backupdouble and source == extfolder and source_member.name.startswith("_home"):
pass
else:
copy_list(source_member, target_member, pathes_done, extfolder, prefix, args)
else:
shutil.copyfile(source_member, target_member)
if args.verbose:
print(f"{source_member.name} copied from {source.relative_to(extfolder)} to {target.relative_to(extfolder)}")
copied_count += 1
if copied_count > 0:
print(f"{copied_count} members copied")
if not args.homedouble:
pathes_done.add(source)
pathes_done.add(target)
Code: Alles auswählen
def parse_args():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-f", "--file", help="Filename for PDS")
parser.add_argument("-p", "--prefix", help="Prefix added before target")
parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
parser.add_argument("-i", "--source", help="Single Source folder, may be .")
parser.add_argument("-o", "--target", help="Single Target folder")
parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default")
parser.add_argument("-r", "--notrecursive", action="store_true", help="Do not copy subdirectories")
parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
parser.add_argument("-t", "--notimestamp", action="store_true", help="Do not Add Timestamp to folder (not advised!)")
parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
args = parser.parse_args()
if args.file and args.source:
parser.error("You cannot use --file and --source at the same time")
if not args.prefix and args.notimestamp:
parser.error("--notimestamp without --prefix not allowed.")
if args.source is None:
if args.target:
target = None
print(f"--target {args.target} ignored")
input_file = Path(args.file if args.file else "copylist.txt")
source = target = None
else:
source = Path(args.source)
target = Path(args.target if args.target else args.source)
folder_timestamp = "" if args.notimestamp else f"_d{datetime.now():%Y%m%d_t%H%M%S}"
return args, source, target, folder_timestamp, input_file
def main():
print("\npycopy start\n")
args, source, target, folder_timestamp, input_file = parse_args()
extfolder = Path(".") if args.extfolder is None else Path(args.extfolder)
prefix = "" if args.prefix is None else args.prefix
if source:
source_items = [ (source, target) ]
else:
source_items = read_source_file(input_file)
copy_items = create_copy_items(source_items, extfolder, prefix, folder_timestamp)
pathes_done = {
source for source, target in copy_items
}
for source,target in copy_items:
copy_list(source, target, pathes_done, extfolder, prefix, args)
print("\npycopy done\n")
if __name__ == "__main__":
main()
Wie schon oben angesprochen, würde ich Stellen wie diese (und Vergleichbare):
so lösen:
Es lohnt sich wie gesagt, sich in Argparse tiefer einzuarbeiten, wenn man etwas komplexere Interfaces bauen will:
https://docs.python.org/3/howto/argpars ... e-tutorial
Code: Alles auswählen
extfolder = Path(".") if args.extfolder is None else Path(args.extfolder)
prefix = "" if args.prefix is None else args.prefix
Code: Alles auswählen
…
parser.add_argument("-p", "--prefix", help="Prefix added before target", type=str, default="")
…
parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default", default=".", type=Path)
…
https://docs.python.org/3/howto/argpars ... e-tutorial
Danke @ Sirius3 für deine verbesserte Variante, ich habe wirklich sehr viel gelernt bei dem ganzen Umbau. Verglichen mit dem Anfang...
Ich habe jetzt deine unteren 3 Codefenster (ohne das nicht mehr gebrauchte extend_path_by_prefix_timestamp) zusammengesetzt, das noch fehlende read_source_file
und die imports hinzugefügt und voila es lief. Ich habe das Programm mal umbenannt in pybackup, was mir ehrlicher erschien. Mal noch ein bißchen testen mit den Verzeichnissen, kann ich am Feiertag machen.
Besonders beeindruckt hat mich, wie elegant du manche Sachen besser geschrieben hast, zB
- with_name
- members.name (anstelle meiner umständlichen Schleife um die Pfade extra loszuwerden)
- das pass anstelle des continue
- und generell die Python-eigenen Schreibweisen, an die am sich erstmal gewöhnen muß, wenn man aus der Pascal und Cobol-Ecke kommt
Ich habe jetzt deine unteren 3 Codefenster (ohne das nicht mehr gebrauchte extend_path_by_prefix_timestamp) zusammengesetzt, das noch fehlende read_source_file
und die imports hinzugefügt und voila es lief. Ich habe das Programm mal umbenannt in pybackup, was mir ehrlicher erschien. Mal noch ein bißchen testen mit den Verzeichnissen, kann ich am Feiertag machen.
Besonders beeindruckt hat mich, wie elegant du manche Sachen besser geschrieben hast, zB
- with_name
- members.name (anstelle meiner umständlichen Schleife um die Pfade extra loszuwerden)
- das pass anstelle des continue
- und generell die Python-eigenen Schreibweisen, an die am sich erstmal gewöhnen muß, wenn man aus der Pascal und Cobol-Ecke kommt