From ed064a49cf6b930e53151dee7ea7ee5536738f30 Mon Sep 17 00:00:00 2001 From: Nicklas Reincke Date: Sat, 15 Dec 2018 10:20:37 +0100 Subject: [PATCH 1/4] Applied Flake8 code style and added explicit error handling --- gedcom/__init__.py | 66 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/gedcom/__init__.py b/gedcom/__init__.py index d6180cd..9117e00 100644 --- a/gedcom/__init__.py +++ b/gedcom/__init__.py @@ -138,7 +138,7 @@ class Gedcom: - a dict (only elements with pointers, which are the keys) """ - def __init__(self, file_path, use_strict = True): + def __init__(self, file_path, use_strict=True): """Initialize a GEDCOM data object. You must supply a GEDCOM file :type file_path: str """ @@ -215,7 +215,7 @@ def get_root_child_elements(self): # Private methods - def __parse(self, file_path, use_strict = True): + def __parse(self, file_path, use_strict=True): """Open and parse file path as GEDCOM 5.5 formatted data :type file_path: str """ @@ -223,11 +223,11 @@ def __parse(self, file_path, use_strict = True): line_number = 1 last_element = self.__root_element for line in gedcom_file: - last_element = self.__parse_line(line_number, line.decode('utf-8-sig'), last_element, use_strict ) + last_element = self.__parse_line(line_number, line.decode('utf-8-sig'), last_element, use_strict) line_number += 1 @staticmethod - def __parse_line(line_number, line, last_element, use_strict = True): + def __parse_line(line_number, line, last_element, use_strict=True): """Parse a line from a GEDCOM 5.5 formatted document Each line should have the following (bracketed items optional): @@ -260,8 +260,8 @@ def __parse_line(line_number, line, last_element, use_strict = True): if regex_match is None: if use_strict: - error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number + - "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") + error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number + + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") raise SyntaxError(error_message) else: # Quirk check - see if this is a line without a CRLF (which could be the last line) @@ -269,16 +269,16 @@ def __parse_line(line_number, line, last_element, use_strict = True): regex_match = regex.match(last_line_regex, line) if regex_match is not None: line_parts = regex_match.groups() - + level = int(line_parts[0]) pointer = line_parts[1].rstrip(' ') tag = line_parts[2] value = line_parts[3][1:] crlf = '\n' else: - # Quirk check - Sometimes a gedcom has a text field with a CR. - # This creates a line without the standard level and pointer. If this is detected - # then turn it into a CONC or CONT + # Quirk check - Sometimes a gedcom has a text field with a CR. + # This creates a line without the standard level and pointer. + # If this is detected then turn it into a CONC or CONT. line_regex = '([^\n\r]*|)' cont_line_regex = line_regex + end_of_line_regex regex_match = regex.match(cont_line_regex, line) @@ -294,7 +294,7 @@ def __parse_line(line_number, line, last_element, use_strict = True): tag = GEDCOM_TAG_CONCATENATION else: line_parts = regex_match.groups() - + level = int(line_parts[0]) pointer = line_parts[1].rstrip(' ') tag = line_parts[2] @@ -303,9 +303,9 @@ def __parse_line(line_number, line, last_element, use_strict = True): # Check level: should never be more than one higher than previous line. if level > last_element.get_level() + 1: - error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number + - "\nLines must be no more than one level higher than previous line." + - "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") + error_message = ("Line %d of document violates GEDCOM format 5.5" % line_number + + "\nLines must be no more than one level higher than previous line." + + "\nSee: https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf") raise SyntaxError(error_message) # Create element. Store in list and dict, create children and parents. @@ -416,9 +416,9 @@ def get_families(self, individual, family_type=GEDCOM_TAG_FAMILY_SPOUSE): families = [] element_dictionary = self.get_element_dictionary() for child_element in individual.get_child_elements(): - is_family = (child_element.get_tag() == family_type and - child_element.get_value() in element_dictionary and - element_dictionary[child_element.get_value()].is_family()) + is_family = (child_element.get_tag() == family_type + and child_element.get_value() in element_dictionary + and element_dictionary[child_element.get_value()].is_family()) if is_family: families.append(element_dictionary[child_element.get_value()]) return families @@ -508,12 +508,12 @@ def get_family_members(self, family, members_type="ALL"): element_dictionary = self.get_element_dictionary() for child_element in family.get_child_elements(): # Default is ALL - is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND or - child_element.get_tag() == GEDCOM_TAG_WIFE or - child_element.get_tag() == GEDCOM_TAG_CHILD) + is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND + or child_element.get_tag() == GEDCOM_TAG_WIFE + or child_element.get_tag() == GEDCOM_TAG_CHILD) if members_type == "PARENTS": - is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND or - child_element.get_tag() == GEDCOM_TAG_WIFE) + is_family = (child_element.get_tag() == GEDCOM_TAG_HUSBAND + or child_element.get_tag() == GEDCOM_TAG_WIFE) elif members_type == "HUSB": is_family = child_element.get_tag() == GEDCOM_TAG_HUSBAND elif members_type == "WIFE": @@ -821,13 +821,15 @@ def criteria_match(self, criteria): :rtype: bool """ - # error checking on the criteria + # Check if criteria is a valid criteria try: for criterion in criteria.split(':'): - key, value = criterion.split('=') - except: + criterion.split('=') + except ValueError: return False + match = True + for criterion in criteria.split(':'): key, value = criterion.split('=') if key == "surname" and not self.surname_match(value): @@ -839,7 +841,7 @@ def criteria_match(self, criteria): year = int(value) if not self.birth_year_match(year): match = False - except: + except ValueError: match = False elif key == "birth_range": try: @@ -848,14 +850,14 @@ def criteria_match(self, criteria): to_year = int(to_year) if not self.birth_range_match(from_year, to_year): match = False - except: + except ValueError: match = False elif key == "death": try: year = int(value) if not self.death_year_match(year): match = False - except: + except ValueError: match = False elif key == "death_range": try: @@ -864,7 +866,7 @@ def criteria_match(self, criteria): to_year = int(to_year) if not self.death_range_match(from_year, to_year): match = False - except: + except ValueError: match = False return match @@ -929,7 +931,7 @@ def get_name(self): last = "" if not self.is_individual(): return first, last - + # Return the first GEDCOM_TAG_NAME that is found. Alternatively # as soon as we have both the GEDCOM_TAG_GIVEN_NAME and _SURNAME return those found_given_name = False @@ -1022,7 +1024,7 @@ def get_birth_year(self): return -1 try: return int(date) - except: + except ValueError: return -1 def get_death_data(self): @@ -1062,7 +1064,7 @@ def get_death_year(self): return -1 try: return int(date) - except: + except ValueError: return -1 def get_burial(self): From be1bf3c6b148818665f41c4adbd3c094407551a2 Mon Sep 17 00:00:00 2001 From: Nicklas Reincke Date: Sat, 15 Dec 2018 10:25:46 +0100 Subject: [PATCH 2/4] Updated project structure --- .gitignore | 402 +---------------------------------------- .travis.yml | 15 ++ Dockerfile | 11 -- LICENSE => LICENSE.txt | 0 MANIFEST.in | 8 + README.md | 145 +++++++-------- docker-compose.yml | 9 - requirements.txt | 1 + setup.cfg | 13 ++ setup.py | 47 ++++- tests/__init__.py | 0 tests/test_simple.py | 2 + tox.ini | 25 +++ 13 files changed, 183 insertions(+), 495 deletions(-) create mode 100644 .travis.yml delete mode 100644 Dockerfile rename LICENSE => LICENSE.txt (100%) create mode 100644 MANIFEST.in delete mode 100644 docker-compose.yml create mode 100644 setup.cfg create mode 100644 tests/__init__.py create mode 100644 tests/test_simple.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 6e7ee36..6f01f73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,402 +1,11 @@ # Ignore .ged files *.ged -# Created by .ignore support plugin (hsz.mobi) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# Due to using tox +.tox -# User-specific stuff: -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/dictionaries - -# Sensitive or high-churn files: -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.xml -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle: -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ - -# Mongo Explorer plugin: -.idea/**/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties -### VisualStudio template -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ -### SublimeText template -# Cache files for Sublime Text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# Workspace files are user-specific -*.sublime-workspace - -# Project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using Sublime Text -# *.sublime-project - -# SFTP configuration file -sftp-config.json - -# Package control specific files -Package Control.last-run -Package Control.ca-list -Package Control.ca-bundle -Package Control.system-ca-bundle -Package Control.cache/ -Package Control.ca-certs/ -Package Control.merged-ca-bundle -Package Control.user-ca-bundle -oscrypto-ca-bundle.crt -bh_unicode_properties.cache - -# Sublime-github package stores a github token in this file -# https://packagecontrol.io/packages/sublime-github -GitHub.sublime-settings -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -### Python template # Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] *$py.class @@ -442,15 +51,16 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +.pytest_cache/ # Translations *.mo *.pot # Django stuff: -.static_storage/ -.media/ +*.log local_settings.py +db.sqlite3 # Flask stuff: instance/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2cb99d7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: python + +matrix: + include: + - python: '2.7' + env: TOXENV=py27 + - python: '3.7' + env: TOXENV=py37 + +install: pip install tox + +script: tox + +notifications: + email: false diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 67169d0..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -ARG PYTHON_VERSION=3.7 -FROM python:${PYTHON_VERSION}-alpine - -WORKDIR /usr/src/app - -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -CMD [ "python", "./gedcom/__init__.py" ] diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..47b3ea0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +# Include the README +include *.md + +# Include the license file +include LICENSE.txt + +# Include the `requirements.txt` file +include requirements.txt diff --git a/README.md b/README.md index e56cbd7..a688eca 100644 --- a/README.md +++ b/README.md @@ -3,30 +3,26 @@ [![PyPI](https://img.shields.io/pypi/v/python-gedcom.svg)](https://pypi.org/project/python-gedcom/) [![GitHub release](https://img.shields.io/github/release/nickreynke/python-gedcom.svg)](https://github.com/nickreynke/python-gedcom/releases) ![](https://img.shields.io/badge/GEDCOM%20format%20version-5.5-yellowgreen.svg) -![](https://img.shields.io/badge/Python%20version-2%20and%203-yellowgreen.svg) +![](https://img.shields.io/badge/Python%20versions-2.7%20and%203.7-yellowgreen.svg) A Python module for parsing, analyzing, and manipulating GEDCOM files. GEDCOM files contain ancestry data. The parser is currently supporting the GEDCOM 5.5 format which is detailed [here](https://chronoplexsoftware.com/gedcomvalidator/gedcom/gedcom-5.5.pdf). -> **NOTE**: This module is currently under development and **should not be used in production**! +> **NOTE**: This module is currently under development and **should not be used in production** yet! > The current development process can be tracked in the ["develop" branch](https://github.com/reynke/python-gedcom/tree/develop). -> ->If you wish to use the pre-production version of the python-gedcom packages, then supply the --pre command line to pip: -> -> pip install python-gedcom --pre ## Installation -> **NOTE**: As of the 26 march of 2018 the beta of the new Python Package -> Index launched. I recommend reading this: "[All New PyPI is now in beta](https://pyfound.blogspot.de/2018/03/warehouse-all-new-pypi-is-now-in-beta.html)" - The module can be installed via [pip](https://pip.pypa.io/). -Run `pip install python-gedcom` to install or `pip install python-gedcom --upgrade` +Run `pip install python-gedcom` to install or `pip install python-gedcom --upgrade` to upgrade to the newest version uploaded to the [PyPI repository](https://pypi.org/project/python-gedcom/). +If you want to use the latest pre-release of the `python-gedcom` package, +simply append the `--pre` option to `pip`: `pip install python-gedcom --pre` + ## Usage When successfully installed you may import the `gedcom` module and use @@ -41,8 +37,7 @@ gedcom = Gedcom(file_path) ### GEDCOM Quirks -Large sites like Ancesty and MyHeritage (among others) don't always produce perfectly formatted GEDCOM files. If you encounter errors in parsing, you might consider disabling strict parsing which will make a best effort to parse the file: - +Large sites like Ancestry and MyHeritage (among others) don't always produce perfectly formatted GEDCOM files. If you encounter errors in parsing, you might consider disabling strict parsing which will make a best effort to parse the file: ```python from gedcom import Gedcom @@ -69,74 +64,80 @@ for record in all_records: ## Reference -The Element class contains all the information for a single record in the GEDCOM file, for example and individual. - -### Single Record Methods - -Method | Parameters | Returns | Description ------------------------|------------|---------|------------ -get_child_elements | none | List of Element | Returns all the child elements of this record -get_parent_element | none | Element | Returns parent Element -new_child_element | String tag, String pointer, String value | Element | Create a new Element -add_child_element | Element child | Element | Adds the child record -set_parent_element | Element parent| none | Not normally required to be called (add_child_element calls this automatically -is_individual | none | Boolean | Returns True if the record is that of a person -is_family | none | Boolean | Returns True if thet record of a family. Family records can be passed to get_family_members() -is_file | none | Boolean | Returns True if the record is a pointer to an external file -is_object | none | Boolean | Returns True if the record is an object (for example multi-media) stored inside the gedcom -is_private | none | Boolean | Returns True if the record is marked Private -is_deceased | none | Boolean | Returns True if the individual is marked deceased -is_child | none | Boolean | Returns True if the individual is a child -criteria_match | colon separated string "surname=[name]:name=[name]:birth][year]:birth_range=[year-to-year]:death=[year]:death_range[year-to-year]"| Boolean | Returns True if the criteria matches -surname_match | String | Boolean | Returns True if the case insensitive substring matches the supplied string -given_match | String | Boolean | Returns True if the case insensitive substring matches the supplied string -death_range_match | Int from, Int to | Boolean | Returns True if Death Year is in the supplied range -death_year_match | Int | Boolean | Returns True if Death Year equals parameter -birth_range_match | Int from, Int to | Boolean | Returns True if Birth Year is in the supplied range -birth_year_match | Int | Boolean | Returns True if Birth Year equals parameter -get_name | none | (String given, String surname) | Returns the Given name(s) and Surname in a tuple -get_gender | none | String | Returns individual's gender -get_birth_data | none | (String date, String place, Array sources) | Returns a tuple of the birth data -get_birth_year | none | Int | Returns the Birth Year -get_death_data | none | (String date, String place, Array sources) | Returns a tuple of the death data -get_death_year | none | Int | Returns the Death Year -get_burial | none | (String date, String place, Array sources) | Returns a tuple of the burial data -get_census | none | List [String date, String place, Array sources] | Returns a List of tuple of the census data -get_last_change_date | none | String | Returns the date of the last update to this individual -get_occupation | none | String | Returns the individual's occupation -get_individual | none | Individual | Returns the individual - -### Gedcom operations - -Method | Parameters | Returns | Description -------------------------|------------|---------|------------ -get_root_element | none | Element root | Returns the virtual "root" individual -get_root_child_elements | none | List of Element | Returns a List of all Elements -get_element_dictionary | none | Dict of Element | Returns a Dict of all Elements -get_element_list | none | List of Element | Returns a List of all Elements -get_marriages | Element individual | List of Marriage ("Date", "Place") | Returns List of Tuples of Marriage data (Date and Place) -find_path_to_ancestors | Element descendant, Element ancestor| List of Element| Returns list of individuals from the descendant Element to the ancestor Element. Returns None if there is no direct path -get_family_members | Element family, optional String members_type - one of "ALL" (default), "PARENTS", "HUSB", "WIFE", "CHIL" | List of Element individuals | Returns a list of individuals for the supplied family record, filtered by the members_type -get_parents | Element individual, optional String parent_type - one of "ALL" (default) or "NAT" | List of Element individuals | Returns the individual's parents as a List -get_ancestors | Element individual, optional String ancestor_type - one of "All" (default) or "NAT" | List of Element individuals | Recursively retrieves all the parents starting with the supplied individual -get_families | Element individual optional String family_type - one of "FAMS" (default), "FAMC"|List of Family records | Family Records can be used in get_family_members() -marriage_range_match | Element individual, Int from, Int to| Boolean | Check if individual is married within the specified range -marriage_year_match | Element individual, Int year| Boolean | Check if individual is married in the year specified -get_marriage_years | Element individual |List of Int| Returns Marriage event years -print_gedcom | none | none | Prints the gedcom to STDOUT -save_gedcom | String filename | none | Writes gedcom to specified filename +The `Element` class contains all the information for a single record in the GEDCOM file, for example and individual. + +### `Element` methods + +Method | Parameters | Returns | Description +-------|------------|---------|------------ +`get_child_elements` | none | List of Element | Returns all the child elements of this record +`get_parent_element` | none | Element | Returns parent Element +`new_child_element` | String tag, String pointer, String value | Element | Create a new Element +`add_child_element` | Element child | Element | Adds the child record +`set_parent_element` | Element parent| none | Not normally required to be called (add_child_element calls this automatically +`is_individual` | none | Boolean | Returns True if the record is that of a person +`is_family` | none | Boolean | Returns True if thet record of a family. Family records can be passed to get_family_members() +`is_file` | none | Boolean | Returns True if the record is a pointer to an external file +`is_object` | none | Boolean | Returns True if the record is an object (for example multi-media) stored inside the gedcom +`is_private` | none | Boolean | Returns True if the record is marked Private +`is_deceased` | none | Boolean | Returns True if the individual is marked deceased +`is_child` | none | Boolean | Returns True if the individual is a child +`criteria_match` | colon separated string "surname=[name]:name=[name]:birth][year]:birth_range=[year-to-year]:death=[year]:death_range[year-to-year]"| Boolean | Returns True if the criteria matches +`surname_match` | String | Boolean | Returns True if the case insensitive substring matches the supplied string +`given_match` | String | Boolean | Returns True if the case insensitive substring matches the supplied string +`death_range_match` | Int from, Int to | Boolean | Returns True if Death Year is in the supplied range +`death_year_match` | Int | Boolean | Returns True if Death Year equals parameter +`birth_range_match` | Int from, Int to | Boolean | Returns True if Birth Year is in the supplied range +`birth_year_match` | Int | Boolean | Returns True if Birth Year equals parameter +`get_name` | none | (String given, String surname) | Returns the Given name(s) and Surname in a tuple +`get_gender` | none | String | Returns individual's gender +`get_birth_data` | none | (String date, String place, Array sources) | Returns a tuple of the birth data +`get_birth_year` | none | Int | Returns the Birth Year +`get_death_data` | none | (String date, String place, Array sources) | Returns a tuple of the death data +`get_death_year` | none | Int | Returns the Death Year +`get_burial` | none | (String date, String place, Array sources) | Returns a tuple of the burial data +`get_census` | none | List [String date, String place, Array sources] | Returns a List of tuple of the census data +`get_last_change_date` | none | String | Returns the date of the last update to this individual +`get_occupation` | none | String | Returns the individual's occupation +`get_individual` | none | Individual | Returns the individual + +### `Gedcom` method + +Method | Parameters | Returns | Description +-------|------------|---------|------------ +`get_root_element` | none | Element root | Returns the virtual "root" individual +`get_root_child_elements` | none | List of Element | Returns a List of all Elements +`get_element_dictionary` | none | Dict of Element | Returns a Dict of all Elements +`get_element_list` | none | List of Element | Returns a List of all Elements +`get_marriages` | Element individual | List of Marriage ("Date", "Place") | Returns List of Tuples of Marriage data (Date and Place) +`find_path_to_ancestors` | Element descendant, Element ancestor| List of Element| Returns list of individuals from the descendant Element to the ancestor Element. Returns None if there is no direct path +`get_family_members` | Element family, optional String members_type - one of "ALL" (default), "PARENTS", "HUSB", "WIFE", "CHIL" | List of Element individuals | Returns a list of individuals for the supplied family record, filtered by the members_type +`get_parents` | Element individual, optional String parent_type - one of "ALL" (default) or "NAT" | List of Element individuals | Returns the individual's parents as a List +`get_ancestors` | Element individual, optional String ancestor_type - one of "All" (default) or "NAT" | List of Element individuals | Recursively retrieves all the parents starting with the supplied individual +`get_families` | Element individual optional String family_type - one of "FAMS" (default), "FAMC"|List of Family records | Family Records can be used in get_family_members() +`marriage_range_match` | Element individual, Int from, Int to| Boolean | Check if individual is married within the specified range +`marriage_year_match` | Element individual, Int year| Boolean | Check if individual is married in the year specified +`get_marriage_years` | Element individual |List of Int| Returns Marriage event years +`print_gedcom` | none | none | Prints the gedcom to STDOUT +`save_gedcom` | String filename | none | Writes gedcom to specified filename ## Local development -Local development is powered with [Docker Compose](https://docs.docker.com/compose/). +### Running tests + +1. Run `pip install --no-cache-dir -r requirements.txt` to install dependencies +1. Run tests with [tox](https://tox.readthedocs.io/en/latest/index.html) + * For Python 2.7 run `tox -e py27` (you need to have Python 2.7 installed) + * For Python 3.7 run `tox -e py37` (you need to have Python 3.7 installed) ### Uploading a new package to PyPI -1. Run `docker-compose run --rm python python3 setup.py sdist bdist_wheel` to generate distribution archives -1. Run `docker-compose run --rm python twine upload --repository-url https://test.pypi.org/legacy/ dist/*` to upload the archives to the Test Python Package Index repository +1. Run `pip install --no-cache-dir -r requirements.txt` to install dependencies +1. Run `python setup.py sdist bdist_wheel` to generate distribution archives +1. Run `twine upload --repository-url https://test.pypi.org/legacy/ dist/*` to upload the archives to the Test Python Package Index repository > When the package is ready to be published to the real Python Package Index -the URL is `repository-url` is `https://upload.pypi.org/legacy/`. +the `repository-url` is `https://upload.pypi.org/legacy/`. ## History diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6d4eb50..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - - python: - build: - context: ./ - volumes: - - ./:/usr/src/app:z diff --git a/requirements.txt b/requirements.txt index 8f3da39..8b2077f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ setuptools wheel twine +tox diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..bb3303f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[metadata] +# This includes the license file(s) in the wheel. +# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file +license_files = LICENSE.txt + +[bdist_wheel] +# This flag says to generate wheels that support both Python 2 and Python +# 3. If your code will not run unchanged on both Python 2 and 3, you will +# need to generate separate wheels for each Python version that you +# support. Removing this line (or setting universal to 0) will prevent +# bdist_wheel from trying to make a universal wheel. For more see: +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels +universal=1 diff --git a/setup.py b/setup.py index 4b902c5..32b27c2 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,47 @@ -from setuptools import setup +from setuptools import setup, find_packages +from os import path +from io import open + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the `README.md` file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() setup( name='python-gedcom', version='0.2.4dev', - packages=['gedcom', ], - license='GPLv2', - package_dir={'': '.'}, description='A Python module for parsing, analyzing, and manipulating GEDCOM files.', - long_description=open('README.md').read(), - maintainer='Nicklas Reincke', - maintainer_email='contact@reynke.com', + long_description=long_description, + long_description_content_type='text/markdown', url='https://github.com/nickreynke/python-gedcom', + author='Nicklas Reincke', + author_email='contact@reynke.com', + license='GPLv2', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Sociology :: Genealogy', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + keywords='python gedcom parser', + packages=find_packages(exclude=['contrib', 'docs', 'tests']), + install_requires=[], + extras_require={ + 'dev': ['setuptools', 'wheel', 'twine'], + 'test': ['tox'], + }, + package_data={}, + data_files=[], + project_urls={ + 'Bug Reports': 'https://github.com/nickreynke/python-gedcom/issues', + 'Source': 'https://github.com/nickreynke/python-gedcom', + }, ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_simple.py b/tests/test_simple.py new file mode 100644 index 0000000..b5d98d0 --- /dev/null +++ b/tests/test_simple.py @@ -0,0 +1,2 @@ +def test_success(): + assert True diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..520f8e6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,25 @@ +# - check-manifest +# confirm items checked into vcs are in your sdist +# - python setup.py check +# confirm required package meta-data in setup.py + +[tox] +envlist = py{27,37} + +[testenv] +basepython = + py27: python2.7 + py37: python3.7 +deps = + check-manifest + flake8 + pytest +commands = + check-manifest --ignore tox.ini,tests* + python setup.py check -m -s + flake8 . + py.test tests +[flake8] +exclude = .tox,*.egg,build,data +select = E,W,F +ignore = E501,W503 From ca888ad3cd5b3a5141607a1db5c626765030d574 Mon Sep 17 00:00:00 2001 From: Nicklas Reincke Date: Sat, 15 Dec 2018 11:22:56 +0100 Subject: [PATCH 3/4] Updated test suite --- .travis.yml | 8 ++++++-- README.md | 6 +++++- setup.py | 1 - tox.ini | 6 ++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2cb99d7..3d1c12e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,12 @@ matrix: include: - python: '2.7' env: TOXENV=py27 - - python: '3.7' - env: TOXENV=py37 + - python: '3.4' + env: TOXENV=py34 + - python: '3.5' + env: TOXENV=py35 + - python: '3.6' + env: TOXENV=py36 install: pip install tox diff --git a/README.md b/README.md index a688eca..8036b28 100644 --- a/README.md +++ b/README.md @@ -123,12 +123,16 @@ Method | Parameters | Returns | Description ## Local development +I suggest using [pyenv](https://github.com/pyenv/pyenv) for local development. + ### Running tests 1. Run `pip install --no-cache-dir -r requirements.txt` to install dependencies 1. Run tests with [tox](https://tox.readthedocs.io/en/latest/index.html) * For Python 2.7 run `tox -e py27` (you need to have Python 2.7 installed) - * For Python 3.7 run `tox -e py37` (you need to have Python 3.7 installed) + * For Python 3.4 run `tox -e py34` (you need to have Python 3.6 installed) + * For Python 3.5 run `tox -e py35` (you need to have Python 3.6 installed) + * For Python 3.6 run `tox -e py36` (you need to have Python 3.6 installed) ### Uploading a new package to PyPI diff --git a/setup.py b/setup.py index 32b27c2..b6776c0 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,6 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', ], keywords='python gedcom parser', packages=find_packages(exclude=['contrib', 'docs', 'tests']), diff --git a/tox.ini b/tox.ini index 520f8e6..c23af96 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,14 @@ # confirm required package meta-data in setup.py [tox] -envlist = py{27,37} +envlist = py{27,34,35,36} [testenv] basepython = py27: python2.7 - py37: python3.7 + py34: python3.4 + py35: python3.5 + py36: python3.6 deps = check-manifest flake8 From 77b2f1fcf2a09df923f12ebcfab73251411b1c42 Mon Sep 17 00:00:00 2001 From: Nicklas Reincke Date: Sat, 15 Dec 2018 11:30:41 +0100 Subject: [PATCH 4/4] Prepared documentation and `setup.py` for version `v0.2.5dev` --- README.md | 14 +++++++++++--- setup.py | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8036b28..246220f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![PyPI](https://img.shields.io/pypi/v/python-gedcom.svg)](https://pypi.org/project/python-gedcom/) [![GitHub release](https://img.shields.io/github/release/nickreynke/python-gedcom.svg)](https://github.com/nickreynke/python-gedcom/releases) +[![Build Status](https://travis-ci.org/nickreynke/python-gedcom.svg?branch=master)](https://travis-ci.org/nickreynke/python-gedcom) ![](https://img.shields.io/badge/GEDCOM%20format%20version-5.5-yellowgreen.svg) ![](https://img.shields.io/badge/Python%20versions-2.7%20and%203.7-yellowgreen.svg) @@ -154,14 +155,21 @@ Further updates by [Nicklas Reincke](https://github.com/nickreynke) and [Damon B ## Changelog +**v0.2.5dev** + +- Updated project structure ([#18](https://github.com/nickreynke/python-gedcom/issues/18)) +- Fixed `setup.py` outputting correct markdown when reading the `README.md` ([#16](https://github.com/nickreynke/python-gedcom/issues/16)) +- Applied Flake8 code style and **added explicit error handling** +- Set up test suite + **v0.2.4dev** -- Make surname_match and given_match case insensitive ([#10](https://github.com/nickreynke/python-gedcom/issues/10)) -- add new is_child method +- Made `surname_match` and `given_match` case insensitive ([#10](https://github.com/nickreynke/python-gedcom/issues/10)) +- Added new `is_child` method ([#10](https://github.com/nickreynke/python-gedcom/issues/10)) **v0.2.3dev** -- Assemble Marriages properly ([#9](https://github.com/nickreynke/python-gedcom/issues/9)) +- Assemble marriages properly ([#9](https://github.com/nickreynke/python-gedcom/issues/9)) - Return the top NAME record instead of the last one ([#9](https://github.com/nickreynke/python-gedcom/issues/9)) **v0.2.2dev** diff --git a/setup.py b/setup.py index b6776c0..d773c98 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='python-gedcom', - version='0.2.4dev', + version='0.2.5dev', description='A Python module for parsing, analyzing, and manipulating GEDCOM files.', long_description=long_description, long_description_content_type='text/markdown',