From: Benoît Date: Tue, 23 Feb 2021 00:13:43 +0000 (+0100) Subject: [ADD] Add packaging with setup.py X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=5f5dc92669970434703fd955984c20c6c672a32d;p=gamesguru%2Fgetmyancestors.git [ADD] Add packaging with setup.py --- diff --git a/.gitignore b/.gitignore index 113d0d5..0679b96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,147 @@ -*.pyc -*.ged +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: *.log -*.settings +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +.floo +.flooignore + +# Redis +dump.rdb + +# Dotfiles +.* +!.gitignore +!.readthedocs.yml + +# vscode .vscode/ -scratchpad.py -/venv/ + +# getmyancestors stuff +*.log +*.settings +*.ged \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4e74823 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +GNU General Public License + +Copyright (C) 2014-2021 Giulio Genovese (giulio.genovese@gmail.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/README.md b/README.md index c95f003..7e14ee3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ getmyancestors ============== -getmyancestors.py is a python3 script that downloads family trees in GEDCOM format from FamilySearch. +_getmyancestors_ is a python3 package that downloads family trees in GEDCOM format from FamilySearch. This program is now in production phase, but bugs might still be present. Features will be added on request. It is provided as is. @@ -9,12 +9,17 @@ The project is maintained at https://github.com/Linekio/getmyancestors. Visit he This script requires python3 and the modules indicated in the requirements.txt file. To install the modules, run in your terminal: -"python3 -m pip install -r requirements.txt" (or "python3 -m pip install --user -r requirements.txt" if you don't have admin rights on your machine). -This script requires python 3.4 (or higher) to run due to some novel features in the argparse and asyncio modules (https://docs.python.org/3/whatsnew/3.4.html) +Installation +============ -To download the script, click on the green button "Clone or download" on the top of this page and then click on "Download ZIP". +The easiest way to install _getmyancestors_ is to use pip: +`pip install getmyancestors` + +Otherwise, you can download the source package and then execute: + +`python3 setup.py install` How to use ========== @@ -22,7 +27,7 @@ How to use With graphical user interface: ``` -python3 fstogedcom.py +fstogedcom ``` Command line examples: @@ -30,48 +35,56 @@ Command line examples: Download four generations of ancestors for the main individual in your tree and output gedcom on stdout (will prompt for username and password): ``` -python3 getmyancestors.py +getmyancestors ``` Download four generations of ancestors and output gedcom to a file while generating a verbode stderr (will prompt for username and password): ``` -python3 getmyancestors.py -o out.ged -v +getmyancestors -o out.ged -v ``` Download four generations of ancestors for individual LF7T-Y4C and generate a verbose log file: ``` -python3 getmyancestors.py -u username -p password -i LF7T-Y4C -o out.ged -l out.log -v +getmyancestors -u username -p password -i LF7T-Y4C -o out.ged -l out.log -v ``` Download six generations of ancestors for individual LF7T-Y4C and generate a verbose log file: ``` -python3 getmyancestors.py -a 6 -u username -p password -i LF7T-Y4C -o out.ged -l out.log -v +getmyancestors -a 6 -u username -p password -i LF7T-Y4C -o out.ged -l out.log -v ``` Download four generations of ancestors for individual LF7T-Y4C including all their children and their children spouses: ``` -python3 getmyancestors.py -d 1 -m -u username -p password -i LF7T-Y4C -o out.ged +getmyancestors -d 1 -m -u username -p password -i LF7T-Y4C -o out.ged ``` Download six generations of ancestors for individuals L4S5-9X4 and LHWG-18F including all their children, grandchildren and their spouses: ``` -python3 getmyancestors.py -a 6 -d 2 -m -u username -p password -i L4S5-9X4 LHWG-18F -o out.ged +getmyancestors -a 6 -d 2 -m -u username -p password -i L4S5-9X4 LHWG-18F -o out.ged ``` Download four generations of ancestors for individual LF7T-Y4C including LDS ordinances (need LDS account) ``` -python3 getmyancestors.py -c -u username -p password -i LF7T-Y4C -o out.ged +getmyancestors -c -u username -p password -i LF7T-Y4C -o out.ged ``` + +Merge two Gedcom files + +``` +mergemyancestors -i file1.ged file2.ged -o out.ged +``` + + Support ======= -Send questions, suggestions, or feature requests to benoitfontaine.ba@gmail.com or giulio.genovese@gmail.com, or open an Issue at https://github.com/Linekio/getmyancestors/issues +Submit questions or suggestions, or feature requests by opening an Issue at https://github.com/Linekio/getmyancestors/issues Donation ======== diff --git a/__init__.py b/__init__.py deleted file mode 100644 index ec1fe35..0000000 --- a/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python3 -# coding: utf-8 diff --git a/getmyancestors/__init__.py b/getmyancestors/__init__.py new file mode 100644 index 0000000..82cd241 --- /dev/null +++ b/getmyancestors/__init__.py @@ -0,0 +1,6 @@ +# coding: utf-8 + +from . import getmyancestors +from . import mergemyancestors + +__version__ = "1.0.1" diff --git a/getmyancestors/__main__.py b/getmyancestors/__main__.py new file mode 100644 index 0000000..3b766b3 --- /dev/null +++ b/getmyancestors/__main__.py @@ -0,0 +1,3 @@ +from getmyancestors import getmyancestors + +getmyancestors.main() diff --git a/fstogedcom.png b/getmyancestors/fstogedcom.png similarity index 100% rename from fstogedcom.png rename to getmyancestors/fstogedcom.png diff --git a/fstogedcom.py b/getmyancestors/fstogedcom.py old mode 100755 new mode 100644 similarity index 86% rename from fstogedcom.py rename to getmyancestors/fstogedcom.py index 0a4ffd4..b6b4fb6 --- a/fstogedcom.py +++ b/getmyancestors/fstogedcom.py @@ -8,15 +8,24 @@ import sys import time import asyncio import tempfile -from tkinter import Tk, StringVar, IntVar, filedialog, messagebox, Menu, TclError, PhotoImage +from tkinter import ( + Tk, + StringVar, + IntVar, + filedialog, + messagebox, + Menu, + TclError, + PhotoImage, +) from tkinter.ttk import Frame, Label, Entry, Button, Checkbutton, Treeview, Notebook from threading import Thread from diskcache import Cache # local import -from getmyancestors import Session, Tree, Indi, Fam -from mergemyancestors import Gedcom -from translation import translations +from getmyancestors.getmyancestors import Session, Tree, Indi, Fam +from getmyancestors.mergemyancestors import Gedcom +from getmyancestors.translation import translations tmp_dir = os.path.join(tempfile.gettempdir(), "fstogedcom") @@ -34,7 +43,7 @@ class EntryWithMenu(Entry): """ Entry widget with right-clic menu to copy/cut/paste """ def __init__(self, master, **kw): - super(EntryWithMenu, self).__init__(master, **kw) + super().__init__(master, **kw) self.bind("", self.click_right) def click_right(self, event): @@ -74,7 +83,7 @@ class FilesToMerge(Treeview): """ List of GEDCOM files to merge """ def __init__(self, master, **kwargs): - super(FilesToMerge, self).__init__(master, selectmode="extended", height=5, **kwargs) + super().__init__(master, selectmode="extended", height=5, **kwargs) self.heading("#0", text=_("Files")) self.column("#0", width=300) self.files = dict() @@ -84,7 +93,8 @@ class FilesToMerge(Treeview): """ add a GEDCOM file """ if any(f.name == filename for f in self.files.values()): messagebox.showinfo( - _("Error"), message=_("File already exist: ") + os.path.basename(filename) + _("Error"), + message=_("File already exist: ") + os.path.basename(filename), ) return if not os.path.exists(filename): @@ -119,7 +129,7 @@ class Merge(Frame): """ Merge GEDCOM widget """ def __init__(self, master, **kwargs): - super(Merge, self).__init__(master, **kwargs) + super().__init__(master, **kwargs) warning = Label( self, font=("a", 7), @@ -194,7 +204,9 @@ class Merge(Frame): tree.indi[fid].baptism = ged.indi[num].baptism tree.indi[fid].confirmation = ged.indi[num].confirmation tree.indi[fid].endowment = ged.indi[num].endowment - if not (tree.indi[fid].sealing_child and tree.indi[fid].sealing_child.famc): + if not ( + tree.indi[fid].sealing_child and tree.indi[fid].sealing_child.famc + ): tree.indi[fid].sealing_child = ged.indi[num].sealing_child # add informations about families @@ -230,7 +242,7 @@ class Merge(Frame): def quit(self): """ prevent exception on quit during download """ - super(Merge, self).quit() + super().quit() os._exit(1) @@ -238,14 +250,16 @@ class SignIn(Frame): """ Sign In widget """ def __init__(self, master, **kwargs): - super(SignIn, self).__init__(master, **kwargs) + super().__init__(master, **kwargs) self.username = StringVar() self.username.set(cache.get("username") or "") self.password = StringVar() label_username = Label(self, text=_("Username:")) entry_username = EntryWithMenu(self, textvariable=self.username, width=30) label_password = Label(self, text=_("Password:")) - entry_password = EntryWithMenu(self, show="●", textvariable=self.password, width=30) + entry_password = EntryWithMenu( + self, show="●", textvariable=self.password, width=30 + ) label_username.grid(row=0, column=0, pady=15, padx=(0, 5)) entry_username.grid(row=0, column=1) label_password.grid(row=1, column=0, padx=(0, 5)) @@ -264,7 +278,7 @@ class StartIndis(Treeview): """ List of starting individuals """ def __init__(self, master, **kwargs): - super(StartIndis, self).__init__( + super().__init__( master, selectmode="extended", height=5, columns=("fid",), **kwargs ) self.heading("#0", text=_("Name")) @@ -282,7 +296,9 @@ class StartIndis(Treeview): messagebox.showinfo(_("Error"), message=_("ID already exist")) return None if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid): - messagebox.showinfo(_("Error"), message=_("Invalid FamilySearch ID: ") + fid) + messagebox.showinfo( + _("Error"), message=_("Invalid FamilySearch ID: ") + fid + ) return None fs = self.master.master.master.fs data = fs.get_url("/platform/tree/persons/%s" % fid) @@ -291,7 +307,9 @@ class StartIndis(Treeview): for name in data["persons"][0]["names"]: if name["preferred"]: self.indis[ - self.insert("", 0, text=name["nameForms"][0]["fullText"], values=fid) + self.insert( + "", 0, text=name["nameForms"][0]["fullText"], values=fid + ) ] = fid return True messagebox.showinfo(_("Error"), message=_("Individual not found")) @@ -319,7 +337,7 @@ class Options(Frame): """ Options form """ def __init__(self, master, ordinances=False, **kwargs): - super(Options, self).__init__(master, **kwargs) + super().__init__(master, **kwargs) self.ancestors = IntVar() self.ancestors.set(4) self.descendants = IntVar() @@ -335,15 +353,21 @@ class Options(Frame): entry_ancestors = EntryWithMenu(self, textvariable=self.ancestors, width=5) label_descendants = Label(self, text=_("Number of generations to descend")) entry_descendants = EntryWithMenu(self, textvariable=self.descendants, width=5) - btn_add_indi = Button(btn, text=_("Add a FamilySearch ID"), command=self.add_indi) + btn_add_indi = Button( + btn, text=_("Add a FamilySearch ID"), command=self.add_indi + ) btn_spouses = Checkbutton( - self, text="\t" + _("Add spouses and couples information"), variable=self.spouses + self, + text="\t" + _("Add spouses and couples information"), + variable=self.spouses, ) btn_ordinances = Checkbutton( self, text="\t" + _("Add Temple information"), variable=self.ordinances ) btn_contributors = Checkbutton( - self, text="\t" + _("Add list of contributors in notes"), variable=self.contributors + self, + text="\t" + _("Add list of contributors in notes"), + variable=self.contributors, ) self.start_indis.grid(row=0, column=0, columnspan=3) entry_fid.grid(row=0, column=0, sticky="w") @@ -374,7 +398,7 @@ class Download(Frame): """ Main widget """ def __init__(self, master, **kwargs): - super(Download, self).__init__(master, borderwidth=20, **kwargs) + super().__init__(master, borderwidth=20, **kwargs) self.fs = None self.tree = None self.logfile = None @@ -384,7 +408,11 @@ class Download(Frame): self.start_time = None info = Frame(self, borderwidth=10) self.info_label = Label( - info, wraplength=350, borderwidth=20, justify="center", font=("a", 10, "bold") + info, + wraplength=350, + borderwidth=20, + justify="center", + font=("a", 10, "bold"), ) self.info_indis = Label(info) self.info_fams = Label(info) @@ -401,9 +429,13 @@ class Download(Frame): self.form = Frame(self) self.sign_in = SignIn(self.form) self.options = None - self.title = Label(self, text=_("Sign In to FamilySearch"), font=("a", 12, "bold")) + self.title = Label( + self, text=_("Sign In to FamilySearch"), font=("a", 12, "bold") + ) buttons = Frame(self) - self.btn_quit = Button(buttons, text=_("Quit"), command=Thread(target=self.quit).start) + self.btn_quit = Button( + buttons, text=_("Quit"), command=Thread(target=self.quit).start + ) self.btn_valid = Button( buttons, text=_("Sign In"), command=self.command_in_thread(self.login) ) @@ -439,7 +471,9 @@ class Download(Frame): username = self.sign_in.username.get() password = self.sign_in.password.get() if not (username and password): - messagebox.showinfo(message=_("Please enter your FamilySearch username and password.")) + messagebox.showinfo( + message=_("Please enter your FamilySearch username and password.") + ) return self.btn_valid.config(state="disabled") self.info(_("Login to FamilySearch...")) @@ -452,7 +486,9 @@ class Download(Frame): timeout=1, ) if not self.fs.logged: - messagebox.showinfo(_("Error"), message=_("The username or password was incorrect")) + messagebox.showinfo( + _("Error"), message=_("The username or password was incorrect") + ) self.btn_valid.config(state="normal") self.info("") return @@ -471,7 +507,9 @@ class Download(Frame): self.options.pack() self.master.change_lang() self.btn_valid.config( - command=self.command_in_thread(self.download), state="normal", text=_("Download") + command=self.command_in_thread(self.download), + state="normal", + text=_("Download"), ) self.options.start_indis.add_indi(self.fs.fid) self.update_needed = False @@ -481,17 +519,20 @@ class Download(Frame): self.update_needed = False if self.logfile: self.logfile.close() - super(Download, self).quit() + super().quit() os._exit(1) def download(self): """ download family tree """ todo = [ - self.options.start_indis.indis[key] for key in sorted(self.options.start_indis.indis) + self.options.start_indis.indis[key] + for key in sorted(self.options.start_indis.indis) ] for fid in todo: if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid): - messagebox.showinfo(_("Error"), message=_("Invalid FamilySearch ID: ") + fid) + messagebox.showinfo( + _("Error"), message=_("Invalid FamilySearch ID: ") + fid + ) return self.start_time = time.time() self.options.destroy() @@ -531,7 +572,9 @@ class Download(Frame): for fid, indi in self.tree.indi.items(): futures.add(loop.run_in_executor(None, indi.get_notes)) if ordi: - futures.add(loop.run_in_executor(None, self.tree.add_ordinances, fid)) + futures.add( + loop.run_in_executor(None, self.tree.add_ordinances, fid) + ) if cont: futures.add(loop.run_in_executor(None, indi.get_contributors)) for fam in self.tree.fam.values(): @@ -576,7 +619,9 @@ class Download(Frame): t = round(time.time() - self.start_time) minutes = t // 60 seconds = t % 60 - self.time.config(text=_("Elapsed time: %s:%s") % (minutes, str(seconds).zfill(2))) + self.time.config( + text=_("Elapsed time: %s:%s") % (minutes, str(seconds).zfill(2)) + ) def update_gui(self): """ update widget """ @@ -590,7 +635,7 @@ class FStoGEDCOM(Notebook): """ Main notebook """ def __init__(self, master, **kwargs): - super(FStoGEDCOM, self).__init__(master, width=400, **kwargs) + super().__init__(master, width=400, **kwargs) self.download = Download(self) self.merge = Merge(self) self.add(self.download, text=_("Download GEDCOM")) @@ -607,10 +652,17 @@ class FStoGEDCOM(Notebook): self.merge.btn_add_file.config(text=_("Add files")) -if __name__ == "__main__": +def main(): root = Tk() root.title("FamilySearch to GEDCOM") if sys.platform != "darwin": - root.iconphoto(True, PhotoImage(file="fstogedcom.png")) + root.iconphoto( + True, + PhotoImage(file=os.path.join(os.path.dirname(__file__), "fstogedcom.png")), + ) fstogedcom = FStoGEDCOM(root) fstogedcom.mainloop() + + +if __name__ == "__main__": + main() diff --git a/getmyancestors.py b/getmyancestors/getmyancestors.py old mode 100755 new mode 100644 similarity index 85% rename from getmyancestors.py rename to getmyancestors/getmyancestors.py index 86c29df..c4422c0 --- a/getmyancestors.py +++ b/getmyancestors/getmyancestors.py @@ -1,25 +1,4 @@ -#!/usr/bin/env python3 # coding: utf-8 -""" - getmyancestors.py - Retrieve GEDCOM data from FamilySearch Tree - Copyright (C) 2014-2016 Giulio Genovese (giulio.genovese@gmail.com) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Written by Giulio Genovese - and by Benoît Fontaine -""" # global import from __future__ import print_function @@ -34,8 +13,8 @@ import requests import babelfish # local import -from translation import translations - +import getmyancestors +from getmyancestors.translation import translations # is subject to change: see https://www.familysearch.org/developers/docs/api/tree/Persons_resource MAX_PERSONS = 200 @@ -108,11 +87,11 @@ def cont(string): class Session: - """ Create a FamilySearch session - :param username and password: valid FamilySearch credentials - :param verbose: True to active verbose mode - :param logfile: a file object or similar - :param timeout: time before retry a request + """Create a FamilySearch session + :param username and password: valid FamilySearch credentials + :param verbose: True to active verbose mode + :param logfile: a file object or similar + :param timeout: time before retry a request """ def __init__(self, username, password, verbose=False, logfile=False, timeout=60): @@ -134,8 +113,8 @@ class Session: self.logfile.write(log) def login(self): - """ retrieve FamilySearch session ID - (https://familysearch.org/developers/docs/guides/oauth2) + """retrieve FamilySearch session ID + (https://familysearch.org/developers/docs/guides/oauth2) """ while True: try: @@ -153,7 +132,11 @@ class Session: self.write_log("Downloading: " + url) r = requests.post( url, - data={"params": params, "userName": self.username, "password": self.password}, + data={ + "params": params, + "userName": self.username, + "password": self.password, + }, allow_redirects=False, ) @@ -230,7 +213,8 @@ class Session: if r.status_code == 403: if ( "message" in r.json()["errors"][0] - and r.json()["errors"][0]["message"] == "Unable to get ordinances." + and r.json()["errors"][0]["message"] + == "Unable to get ordinances." ): self.write_log( "Unable to get ordinances. " @@ -260,8 +244,8 @@ class Session: self.display_name = data["users"][0]["displayName"] def _(self, string): - """ translate a string into user's language - TODO replace translation file for gettext format + """translate a string into user's language + TODO replace translation file for gettext format """ if string in translations and self.lang in translations[string]: return translations[string][self.lang] @@ -269,10 +253,10 @@ class Session: class Note: - """ GEDCOM Note class - :param text: the Note content - :param tree: a Tree object - :param num: the GEDCOM identifier + """GEDCOM Note class + :param text: the Note content + :param tree: a Tree object + :param num: the GEDCOM identifier """ counter = 0 @@ -298,10 +282,10 @@ class Note: class Source: - """ GEDCOM Source class - :param data: FS Source data - :param tree: a Tree object - :param num: the GEDCOM identifier + """GEDCOM Source class + :param data: FS Source data + :param tree: a Tree object + :param num: the GEDCOM identifier """ counter = 0 @@ -351,9 +335,9 @@ class Source: class Fact: - """ GEDCOM Fact class - :param data: FS Fact data - :param tree: a tree object + """GEDCOM Fact class + :param data: FS Fact data + :param tree: a tree object """ def __init__(self, data=None, tree=None): @@ -378,12 +362,14 @@ class Fact: self.map = tree.places[place["description"][1:]] if "changeMessage" in data["attribution"]: self.note = Note(data["attribution"]["changeMessage"], tree) - if self.type == "http://gedcomx.org/Death" and not (self.date or self.place): + if self.type == "http://gedcomx.org/Death" and not ( + self.date or self.place + ): self.value = "Y" def print(self, file=sys.stdout): - """ print Fact in GEDCOM format - the GEDCOM TAG depends on the type, defined in FACT_TAGS + """print Fact in GEDCOM format + the GEDCOM TAG depends on the type, defined in FACT_TAGS """ if self.type in FACT_TAGS: tmp = "1 " + FACT_TAGS[self.type] @@ -408,8 +394,8 @@ class Fact: class Memorie: - """ GEDCOM Memorie class - :param data: FS Memorie data + """GEDCOM Memorie class + :param data: FS Memorie data """ def __init__(self, data=None): @@ -419,9 +405,9 @@ class Memorie: if "titles" in data: self.description = data["titles"][0]["value"] if "descriptions" in data: - self.description = ("" if not self.description else self.description + "\n") + data[ - "descriptions" - ][0]["value"] + self.description = ( + "" if not self.description else self.description + "\n" + ) + data["descriptions"][0]["value"] def print(self, file=sys.stdout): """ print Memorie in GEDCOM format """ @@ -433,9 +419,9 @@ class Memorie: class Name: - """ GEDCOM Name class - :param data: FS Name data - :param tree: a Tree object + """GEDCOM Name class + :param data: FS Name data + :param tree: a Tree object """ def __init__(self, data=None, tree=None): @@ -459,8 +445,8 @@ class Name: self.note = Note(data["attribution"]["changeMessage"], tree) def print(self, file=sys.stdout, typ=None): - """ print Name in GEDCOM format - :param typ: type for additional names + """print Name in GEDCOM format + :param typ: type for additional names """ tmp = "1 NAME %s /%s/" % (self.given, self.surname) if self.suffix: @@ -475,8 +461,8 @@ class Name: class Ordinance: - """ GEDCOM Ordinance class - :param data: FS Ordinance data + """GEDCOM Ordinance class + :param data: FS Ordinance data """ def __init__(self, data=None): @@ -501,10 +487,10 @@ class Ordinance: class Indi: - """ GEDCOM individual class - :param fid' FamilySearch id - :param tree: a tree object - :param num: the GEDCOM identifier + """GEDCOM individual class + :param fid' FamilySearch id + :param tree: a tree object + :param num: the GEDCOM identifier """ counter = 0 @@ -574,7 +560,9 @@ class Indi: else: self.facts.add(Fact(x, self.tree)) if "sources" in data: - sources = self.tree.fs.get_url("/platform/tree/persons/%s/sources" % self.fid) + sources = self.tree.fs.get_url( + "/platform/tree/persons/%s/sources" % self.fid + ) if sources: quotes = dict() for quote in sources["persons"][0]["sources"]: @@ -586,7 +574,9 @@ class Indi: for source in sources["sourceDescriptions"]: if source["id"] not in self.tree.sources: self.tree.sources[source["id"]] = Source(source, self.tree) - self.sources.add((self.tree.sources[source["id"]], quotes[source["id"]])) + self.sources.add( + (self.tree.sources[source["id"]], quotes[source["id"]]) + ) if "evidence" in data: url = "/platform/tree/persons/%s/memories" % self.fid memorie = self.tree.fs.get_url(url) @@ -595,7 +585,8 @@ class Indi: if x["mediaType"] == "text/plain": text = "\n".join( val.get("value", "") - for val in x.get("titles", []) + x.get("descriptions", []) + for val in x.get("titles", []) + + x.get("descriptions", []) ) self.notes.add(Note(text, self.tree)) else: @@ -619,8 +610,8 @@ class Indi: self.notes.add(Note(text_note, self.tree)) def get_ordinances(self): - """ retrieve LDS ordinances - need a LDS account + """retrieve LDS ordinances + need a LDS account """ res = [] famc = False @@ -660,7 +651,10 @@ class Indi: for contributors in entries["contributors"]: temp.add(contributors["name"]) if temp: - text = "=== %s ===\n%s" % (self.tree.fs._("Contributors"), "\n".join(sorted(temp))) + text = "=== %s ===\n%s" % ( + self.tree.fs._("Contributors"), + "\n".join(sorted(temp)), + ) for n in self.tree.notes: if n.text == text: self.notes.add(n) @@ -715,11 +709,11 @@ class Indi: class Fam: - """ GEDCOM family class - :param husb: husbant fid - :param wife: wife fid - :param tree: a Tree object - :param num: a GEDCOM identifier + """GEDCOM family class + :param husb: husbant fid + :param wife: wife fid + :param tree: a Tree object + :param num: a GEDCOM identifier """ counter = 0 @@ -747,8 +741,8 @@ class Fam: self.chil_fid.add(child) def add_marriage(self, fid): - """ retrieve and add marriage information - :param fid: the marriage fid + """retrieve and add marriage information + :param fid: the marriage fid """ if not self.fid: self.fid = fid @@ -776,14 +770,20 @@ class Fam: source["id"] in new_sources and source["id"] not in self.tree.sources ): - self.tree.sources[source["id"]] = Source(source, self.tree) + self.tree.sources[source["id"]] = Source( + source, self.tree + ) for source_fid in quotes: - self.sources.add((self.tree.sources[source_fid], quotes[source_fid])) + self.sources.add( + (self.tree.sources[source_fid], quotes[source_fid]) + ) def get_notes(self): """ retrieve marriage notes """ if self.fid: - notes = self.tree.fs.get_url("/platform/tree/couple-relationships/%s/notes" % self.fid) + notes = self.tree.fs.get_url( + "/platform/tree/couple-relationships/%s/notes" % self.fid + ) if notes: for n in notes["relationships"][0]["notes"]: text_note = "=== %s ===\n" % n["subject"] if "subject" in n else "" @@ -795,13 +795,18 @@ class Fam: if self.fid: temp = set() url = "/platform/tree/couple-relationships/%s/changes" % self.fid - data = self.tree.fs.get_url(url, {"Accept": "application/x-gedcomx-atom+json"}) + data = self.tree.fs.get_url( + url, {"Accept": "application/x-gedcomx-atom+json"} + ) if data: for entries in data["entries"]: for contributors in entries["contributors"]: temp.add(contributors["name"]) if temp: - text = "=== %s ===\n%s" % (self.tree.fs._("Contributors"), "\n".join(sorted(temp))) + text = "=== %s ===\n%s" % ( + self.tree.fs._("Contributors"), + "\n".join(sorted(temp)), + ) for n in self.tree.notes: if n.text == text: self.notes.add(n) @@ -833,8 +838,8 @@ class Fam: class Tree: - """ family tree class - :param fs: a Session object + """family tree class + :param fs: a Session object """ def __init__(self, fs=None): @@ -850,15 +855,17 @@ class Tree: self.lang = babelfish.Language.fromalpha2(fs.lang).name def add_indis(self, fids): - """ add individuals to the family tree - :param fids: an iterable of fid + """add individuals to the family tree + :param fids: an iterable of fid """ async def add_datas(loop, data): futures = set() for person in data["persons"]: self.indi[person["id"]] = Indi(person["id"], self) - futures.add(loop.run_in_executor(None, self.indi[person["id"]].add_data, person)) + futures.add( + loop.run_in_executor(None, self.indi[person["id"]].add_data, person) + ) for future in futures: await future @@ -880,8 +887,12 @@ class Tree: loop.run_until_complete(add_datas(loop, data)) if "childAndParentsRelationships" in data: for rel in data["childAndParentsRelationships"]: - father = rel["parent1"]["resourceId"] if "parent1" in rel else None - mother = rel["parent2"]["resourceId"] if "parent2" in rel else None + father = ( + rel["parent1"]["resourceId"] if "parent1" in rel else None + ) + mother = ( + rel["parent2"]["resourceId"] if "parent2" in rel else None + ) child = rel["child"]["resourceId"] if "child" in rel else None if child in self.indi: self.indi[child].parents.add((father, mother)) @@ -896,24 +907,28 @@ class Tree: person2 = rel["person2"]["resourceId"] relfid = rel["id"] if person1 in self.indi: - self.indi[person1].spouses.add((person1, person2, relfid)) + self.indi[person1].spouses.add( + (person1, person2, relfid) + ) if person2 in self.indi: - self.indi[person2].spouses.add((person1, person2, relfid)) + self.indi[person2].spouses.add( + (person1, person2, relfid) + ) new_fids = new_fids[MAX_PERSONS:] def add_fam(self, father, mother): - """ add a family to the family tree - :param father: the father fid or None - :param mother: the mother fid or None + """add a family to the family tree + :param father: the father fid or None + :param mother: the mother fid or None """ if (father, mother) not in self.fam: self.fam[(father, mother)] = Fam(father, mother, self) def add_trio(self, father, mother, child): - """ add a children relationship to the family tree - :param father: the father fid or None - :param mother: the mother fid or None - :param child: the child fid or None + """add a children relationship to the family tree + :param father: the father fid or None + :param mother: the mother fid or None + :param child: the child fid or None """ if father in self.indi: self.indi[father].add_fams((father, mother)) @@ -925,8 +940,8 @@ class Tree: self.fam[(father, mother)].add_child(child) def add_parents(self, fids): - """ add parents relationships - :param fids: a set of fids + """add parents relationships + :param fids: a set of fids """ parents = set() for fid in fids & self.indi.keys(): @@ -948,8 +963,8 @@ class Tree: return set(filter(None, parents)) def add_spouses(self, fids): - """ add spouse relationships - :param fids: a set of fid + """add spouse relationships + :param fids: a set of fid """ async def add(loop, rels): @@ -957,7 +972,9 @@ class Tree: for father, mother, relfid in rels: if (father, mother) in self.fam: futures.add( - loop.run_in_executor(None, self.fam[(father, mother)].add_marriage, relfid) + loop.run_in_executor( + None, self.fam[(father, mother)].add_marriage, relfid + ) ) for future in futures: await future @@ -967,7 +984,9 @@ class Tree: rels |= self.indi[fid].spouses loop = asyncio.get_event_loop() if rels: - self.add_indis(set.union(*({father, mother} for father, mother, relfid in rels))) + self.add_indis( + set.union(*({father, mother} for father, mother, relfid in rels)) + ) for father, mother, _ in rels: if father in self.indi and mother in self.indi: self.indi[father].add_fams((father, mother)) @@ -976,8 +995,8 @@ class Tree: loop.run_until_complete(add(loop, rels)) def add_children(self, fids): - """ add children relationships - :param fids: a set of fid + """add children relationships + :param fids: a set of fid """ rels = set() for fid in fids & self.indi.keys(): @@ -999,8 +1018,8 @@ class Tree: return children def add_ordinances(self, fid): - """ retrieve ordinances - :param fid: an individual fid + """retrieve ordinances + :param fid: an individual fid """ if fid in self.indi: ret, famc = self.indi[fid].get_ordinances() @@ -1034,10 +1053,10 @@ class Tree: file.write("0 HEAD\n") file.write("1 CHAR UTF-8\n") file.write("1 GEDC\n") - file.write("2 VERS 5.5.1\n") + file.write("2 VERS 5.1.1\n") file.write("2 FORM LINEAGE-LINKED\n") file.write("1 SOUR getmyancestors\n") - file.write("2 VERS 1.0\n") + file.write("2 VERS %s\n" % getmyancestors.__version__) file.write("2 NAME getmyancestors\n") file.write("1 DATE %s\n" % time.strftime("%d %b %Y")) file.write("2 TIME %s\n" % time.strftime("%H:%M:%S")) @@ -1066,10 +1085,14 @@ def main(): parser = argparse.ArgumentParser( description="Retrieve GEDCOM data from FamilySearch Tree (4 Jul 2016)", add_help=False, - usage="getmyancestors.py -u username -p password [options]", + usage="getmyancestors -u username -p password [options]", + ) + parser.add_argument( + "-u", "--username", metavar="", type=str, help="FamilySearch username" + ) + parser.add_argument( + "-p", "--password", metavar="", type=str, help="FamilySearch password" ) - parser.add_argument("-u", "--username", metavar="", type=str, help="FamilySearch username") - parser.add_argument("-p", "--password", metavar="", type=str, help="FamilySearch password") parser.add_argument( "-i", "--individuals", @@ -1123,7 +1146,12 @@ def main(): help="Increase output verbosity [False]", ) parser.add_argument( - "-t", "--timeout", metavar="", type=int, default=60, help="Timeout in seconds [60]" + "-t", + "--timeout", + metavar="", + type=int, + default=60, + help="Timeout in seconds [60]", ) parser.add_argument( "--show-password", @@ -1131,6 +1159,12 @@ def main(): default=False, help="Show password in .settings file [False]", ) + parser.add_argument( + "--save-settings", + action="store_true", + default=False, + help="Save settings into file [False]", + ) try: parser.add_argument( "-o", @@ -1165,15 +1199,19 @@ def main(): if not re.match(r"[A-Z0-9]{4}-[A-Z0-9]{3}", fid): sys.exit("Invalid FamilySearch ID: " + fid) - args.username = args.username if args.username else input("Enter FamilySearch username: ") + args.username = ( + args.username if args.username else input("Enter FamilySearch username: ") + ) args.password = ( - args.password if args.password else getpass.getpass("Enter FamilySearch password: ") + args.password + if args.password + else getpass.getpass("Enter FamilySearch password: ") ) time_count = time.time() - # Report settings used when getmyancestors.py is executed. - if args.outfile.name != "": + # Report settings used when getmyancestors is executed. + if args.save_settings and args.outfile.name != "": def parse_action(act): if not args.show_password and act.dest == "password": @@ -1185,13 +1223,19 @@ def main(): settings_name = args.outfile.name.split(".")[0] + ".settings" try: with open(settings_name, "w") as settings_file: - settings_file.write(formatting.format("time stamp: ", time.strftime("%X %x %Z"))) + settings_file.write( + formatting.format("time stamp: ", time.strftime("%X %x %Z")) + ) for action in parser._actions: settings_file.write( - formatting.format(action.option_strings[-1], parse_action(action)) + formatting.format( + action.option_strings[-1], parse_action(action) + ) ) except OSError as exc: - print("Unable to write %s: %s" % (settings_name, repr(exc)), file=sys.stderr) + print( + "Unable to write %s: %s" % (settings_name, repr(exc)), file=sys.stderr + ) # initialize a FamilySearch session and a family tree object print("Login to FamilySearch...", file=sys.stderr) @@ -1203,7 +1247,9 @@ def main(): # check LDS account if args.get_ordinances: - test = fs.get_url("/service/tree/tree-data/reservations/person/%s/ordinances" % fs.fid, {}) + test = fs.get_url( + "/service/tree/tree-data/reservations/person/%s/ordinances" % fs.fid, {} + ) if test["status"] != "OK": sys.exit(2) @@ -1220,8 +1266,10 @@ def main(): if not todo: break done |= todo - print(_("Downloading %s. of generations of ancestors...") % (i + 1), - file=sys.stderr) + print( + _("Downloading %s. of generations of ancestors...") % (i + 1), + file=sys.stderr, + ) todo = tree.add_parents(todo) - done # download descendants @@ -1231,14 +1279,15 @@ def main(): if not todo: break done |= todo - print(_("Downloading %s. of generations of descendants...") % - (i + 1), file=sys.stderr) + print( + _("Downloading %s. of generations of descendants...") % (i + 1), + file=sys.stderr, + ) todo = tree.add_children(todo) - done # download spouses if args.marriage: - print(_("Downloading spouses and marriage information..."), - file=sys.stderr) + print(_("Downloading spouses and marriage information..."), file=sys.stderr) todo = set(tree.indi.keys()) tree.add_spouses(todo) @@ -1268,7 +1317,7 @@ def main(): ) + (_(" and contributors") if args.get_contributors else "") + "...", - file=sys.stderr + file=sys.stderr, ) loop.run_until_complete(download_stuff(loop)) @@ -1289,7 +1338,7 @@ def main(): str(round(time.time() - time_count)), str(fs.counter), ), - file=sys.stderr + file=sys.stderr, ) diff --git a/mergemyancestors.py b/getmyancestors/mergemyancestors.py old mode 100755 new mode 100644 similarity index 92% rename from mergemyancestors.py rename to getmyancestors/mergemyancestors.py index 235dd0d..d6f4f27 --- a/mergemyancestors.py +++ b/getmyancestors/mergemyancestors.py @@ -1,25 +1,4 @@ -#!/usr/bin/env python3 # coding: utf-8 -""" - mergemyancestors.py - Merge GEDCOM data from FamilySearch Tree - Copyright (C) 2014-2016 Giulio Genovese (giulio.genovese@gmail.com) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Written by Giulio Genovese - and by Benoît Fontaine -""" from __future__ import print_function @@ -29,7 +8,7 @@ import sys import argparse # local import -import getmyancestors as gt +import getmyancestors.getmyancestors as gt sys.path.append(os.path.dirname(sys.argv[0])) @@ -96,8 +75,8 @@ class Gedcom: self.flag = True def __get_line(self): - """ Parse a new line - If the flag is set, skip reading a newline + """Parse a new line + If the flag is set, skip reading a newline """ if self.flag: self.flag = False @@ -344,16 +323,20 @@ class Gedcom: self.fam[num].chil_fid.add(self.indi[chil].fid) for num in self.indi: for famc in self.indi[num].famc_num: - self.indi[num].famc_fid.add((self.fam[famc].husb_fid, self.fam[famc].wife_fid)) + self.indi[num].famc_fid.add( + (self.fam[famc].husb_fid, self.fam[famc].wife_fid) + ) for fams in self.indi[num].fams_num: - self.indi[num].fams_fid.add((self.fam[fams].husb_fid, self.fam[fams].wife_fid)) + self.indi[num].fams_fid.add( + (self.fam[fams].husb_fid, self.fam[fams].wife_fid) + ) -if __name__ == "__main__": +def main(): parser = argparse.ArgumentParser( description="Merge GEDCOM data from FamilySearch Tree (4 Jul 2016)", add_help=False, - usage="mergemyancestors.py -i input1.ged input2.ged ... [options]", + usage="mergemyancestors -i input1.ged input2.ged ... [options]", ) try: parser.add_argument( @@ -361,7 +344,7 @@ if __name__ == "__main__": metavar="", nargs="+", type=argparse.FileType("r", encoding="UTF-8"), - default=sys.stdin, + default=[sys.stdin], help="input GEDCOM files [stdin]", ) parser.add_argument( @@ -453,3 +436,7 @@ if __name__ == "__main__": # compute number for family relationships and print GEDCOM file tree.reset_num() tree.print(args.o) + + +if __name__ == "__main__": + main() diff --git a/translation.py b/getmyancestors/translation.py similarity index 98% rename from translation.py rename to getmyancestors/translation.py index 8d6139b..a7d4b63 100644 --- a/translation.py +++ b/getmyancestors/translation.py @@ -97,7 +97,9 @@ translations = { "ru": "Название племени", "zh": "部落名字", }, - "Downloading starting individuals...": {"fr": "Téléchargement des personnes de départ..."}, + "Downloading starting individuals...": { + "fr": "Téléchargement des personnes de départ..." + }, "Downloading %s. of generations of descendants...": { "fr": "Téléchargement de %s génération(s) de descendants..." }, diff --git a/requirements.txt b/requirements.txt index 06940ae..279edab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,3 @@ babelfish==0.5.5 -certifi==2019.11.28 -chardet==3.0.4 -diskcache==4.1.0 -idna==2.9 -requests==2.23.0 -urllib3==1.25.8 +diskcache==5.2.1 +requests==2.25.1 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..676bae6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[metadata] +name = getmyancestors +version = attr: getmyancestors.__version__ +description = Retrieve GEDCOM data from FamilySearch Tree +long_description = file:README.md +long_description_content_type = text/markdown +url = https://github.com/Linekio/getmyancestors +license = GNU +classifiers = + Environment :: Console + License :: OSI Approved :: GNU General Public License (GPL) + Operating System :: OS Independent + Programming Language :: Python :: 3 :: Only + +[options] +packages = getmyancestors +install_requires = + babelfish==0.5.5 + diskcache==5.2.1 + requests==2.25.1 + +[options.package_data] +* = fstogedcom.png + +[options.entry_points] +console_scripts = + getmyancestors=getmyancestors.getmyancestors:main + mergemyancestors=getmyancestors.mergemyancestors:main + fstogedcom=getmyancestors.fstogedcom:main + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b908cbe --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +import setuptools + +setuptools.setup()