From ed6c789231138b1851ad8f39400fb27723f47a6f Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 18:08:00 -0400 Subject: [PATCH 1/7] Reformat with black The Makefile has linted and formatted with black for a while, but the code was still in yapf's style and the CI workflow still ran yapf. Reformat the package so the two agree, drop the [yapf] section from setup.cfg, and set flake8's line length and E203 to match black. No functional change. Co-Authored-By: Claude Opus 5 (1M context) --- reference_handler/__init__.py | 4 +- reference_handler/latex_utf8.py | 93 ++- reference_handler/reference_handler.py | 533 +++++++++--------- reference_handler/tests/test_exceptions.py | 21 +- reference_handler/tests/test_latex.py | 270 ++++----- .../tests/test_reference_handler.py | 212 ++++--- reference_handler/utils.py | 19 +- setup.cfg | 10 +- 8 files changed, 555 insertions(+), 607 deletions(-) diff --git a/reference_handler/__init__.py b/reference_handler/__init__.py index 7e93eff..d142fc0 100644 --- a/reference_handler/__init__.py +++ b/reference_handler/__init__.py @@ -12,6 +12,6 @@ from ._version import get_versions versions = get_versions() -__version__ = versions['version'] -__git_revision__ = versions['full-revisionid'] +__version__ = versions["version"] +__git_revision__ = versions["full-revisionid"] del get_versions, versions diff --git a/reference_handler/latex_utf8.py b/reference_handler/latex_utf8.py index a86eca8..6cf0034 100644 --- a/reference_handler/latex_utf8.py +++ b/reference_handler/latex_utf8.py @@ -70,54 +70,53 @@ import typing import unicodedata -alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" # LaTeX characters in accent macros of form \{}, i.e. \"{u} accent = { - '"': '\N{Combining Diaeresis}', - "'": '\N{Combining Acute Accent}', - '.': '\N{Combining Dot Above}', - '=': '\N{Combining Macron}', - '^': '\N{Combining Circumflex Accent}', - '`': '\N{Combining Grave Accent}', - '|': '\N{Combining Vertical Line Above}', - '~': '\N{Combining Tilde}', - 'b': '\N{Combining Macron Below}', - 'c': '\N{Combining Cedilla}', - 'C': '\N{Combining Double Grave Accent}', - 'd': '\N{Combining Dot Below}', - 'f': '\N{Combining Inverted Breve}', - 'h': '\N{Combining Hook Above}', - 'H': '\N{Combining Double Acute Accent}', - 'k': '\N{Combining Ogonek}', - 'r': '\N{Combining Ring Above}', - 't': '\N{Combining Double Inverted Breve}', - 'u': '\N{Combining Breve}', - 'U': '\N{Combining Double Vertical Line Above}', - 'v': '\N{Combining Caron}' + '"': "\N{COMBINING DIAERESIS}", + "'": "\N{COMBINING ACUTE ACCENT}", + ".": "\N{COMBINING DOT ABOVE}", + "=": "\N{COMBINING MACRON}", + "^": "\N{COMBINING CIRCUMFLEX ACCENT}", + "`": "\N{COMBINING GRAVE ACCENT}", + "|": "\N{COMBINING VERTICAL LINE ABOVE}", + "~": "\N{COMBINING TILDE}", + "b": "\N{COMBINING MACRON BELOW}", + "c": "\N{COMBINING CEDILLA}", + "C": "\N{COMBINING DOUBLE GRAVE ACCENT}", + "d": "\N{COMBINING DOT BELOW}", + "f": "\N{COMBINING INVERTED BREVE}", + "h": "\N{COMBINING HOOK ABOVE}", + "H": "\N{COMBINING DOUBLE ACUTE ACCENT}", + "k": "\N{COMBINING OGONEK}", + "r": "\N{COMBINING RING ABOVE}", + "t": "\N{COMBINING DOUBLE INVERTED BREVE}", + "u": "\N{COMBINING BREVE}", + "U": "\N{COMBINING DOUBLE VERTICAL LINE ABOVE}", + "v": "\N{COMBINING CARON}", } # The regexp to detect the LaTeX accent commands. The two added characters # are the dotless i and j. -accent_re = re.compile( - r'\\([' + ''.join(accent.keys()) + r']){([a-zA-Z\u0131\u0237])}' -) +accent_re = re.compile(r"\\([" + "".join(accent.keys()) + r"]){([a-zA-Z\u0131\u0237])}") # Invert the accent dictionary for each letter in the alphabet to make # a dictionary of LaTeX encodings. encoding = {} for key, val in accent.items(): for char in list(alphabet): - encoding[char + val] = '\\' + key + '{' + char + '}' + encoding[char + val] = "\\" + key + "{" + char + "}" # handle any precombined versions of the character - string = unicodedata.normalize('NFC', char + val) + string = unicodedata.normalize("NFC", char + val) if len(string) == 1: encoding[string] = encoding[char + val] for char in [ - '\N{Latin Small Letter Dotless I}', '\N{Latin Small Letter Dotless J}' + "\N{LATIN SMALL LETTER DOTLESS I}", + "\N{LATIN SMALL LETTER DOTLESS J}", ]: - encoding[char + val] = r'\%s{%s}' % (key, char) - string = unicodedata.normalize('NFC', char + val) + encoding[char + val] = r"\%s{%s}" % (key, char) + string = unicodedata.normalize("NFC", char + val) # These characters have no precombined versions, but check anyway if len(string) == 1: encoding[string] = encoding[char + val] @@ -137,20 +136,20 @@ def _decode_latex_accent(match: typing.Match) -> str: # LaTeX single character symbols symbol = { - 'i': '\N{Latin Small Letter Dotless I}', - 'j': '\N{Latin Small Letter Dotless J}', - 'l': '\N{Latin Small Letter L With Stroke}', - 'L': '\N{Latin Capital Letter L With Stroke}', - 'o': '\N{Latin Small Letter O With Stroke}', - 'O': '\N{Latin Capital Letter O With Stroke}' + "i": "\N{LATIN SMALL LETTER DOTLESS I}", + "j": "\N{LATIN SMALL LETTER DOTLESS J}", + "l": "\N{LATIN SMALL LETTER L WITH STROKE}", + "L": "\N{LATIN CAPITAL LETTER L WITH STROKE}", + "o": "\N{LATIN SMALL LETTER O WITH STROKE}", + "O": "\N{LATIN CAPITAL LETTER O WITH STROKE}", } # The regexp to detect the LaTeX commands for special characters -symbol_re = re.compile(r'\\([' + ''.join(symbol.keys()) + '])') +symbol_re = re.compile(r"\\([" + "".join(symbol.keys()) + "])") # Invert the symbol dictionary to make a dictionary of LaTeX encodings. for key, val in symbol.items(): - encoding[val] = '\\' + key + encoding[val] = "\\" + key def _decode_latex_symbol(match: typing.Match) -> str: @@ -166,10 +165,10 @@ def _decode_latex_symbol(match: typing.Match) -> str: # LaTeX dashes -dash = {'--': '\N{EN Dash}', '---': '\N{EM Dash}'} +dash = {"--": "\N{EN DASH}", "---": "\N{EM DASH}"} # The regexp to detect the LaTeX commands for dashes -dash_re = re.compile(r'([^-]?)(-{2,3})([^-]?)') +dash_re = re.compile(r"([^-]?)(-{2,3})([^-]?)") # Invert the dictionary tomake a dictionary of LaTeX encodings. for key, val in dash.items(): @@ -205,13 +204,11 @@ def decode_latex(text: str) -> str: """ return brace_re.sub( - r'\1', + r"\1", accent_re.sub( _decode_latex_accent, - symbol_re.sub( - _decode_latex_symbol, dash_re.sub(_decode_latex_dash, text) - ) - ) + symbol_re.sub(_decode_latex_symbol, dash_re.sub(_decode_latex_dash, text)), + ), ) @@ -228,7 +225,7 @@ def encode_latex(text: str) -> str: """ # Map the double character representations - text2 = '' + text2 = "" char1 = text[0] i = 1 len_text = len(text) @@ -250,7 +247,7 @@ def encode_latex(text: str) -> str: text2 += char1 # Map the single characters representations - result = '' + result = "" for char in list(text2): if char in encoding: result += encoding[char] @@ -260,7 +257,7 @@ def encode_latex(text: str) -> str: return result -if __name__ == '__main__': # pragma: no cover +if __name__ == "__main__": # pragma: no cover text = r""" (Vorlova_2015) Barbora Vorlov{\'{a}} and Dana Nachtigallov{\'{a}} and Jana Jir{\'{a}}skov{\'{a}}-Van{\'{\i}}{\v{c}}kov{\'{a}} and Haresh Ajani and Petr diff --git a/reference_handler/reference_handler.py b/reference_handler/reference_handler.py index 566ef6f..8b02eb7 100644 --- a/reference_handler/reference_handler.py +++ b/reference_handler/reference_handler.py @@ -13,143 +13,143 @@ from .latex_utf8 import decode_latex from .utils import entry_to_bibtex -supported_fmts = ['bibtex', 'text'] +supported_fmts = ["bibtex", "text"] # '-' must be first for the regex to work. subscript = { - '-': '\N{Subscript Minus}', - '0': '\N{Subscript Zero}', - '1': '\N{Subscript One}', - '2': '\N{Subscript Two}', - '3': '\N{Subscript Three}', - '4': '\N{Subscript Four}', - '5': '\N{Subscript Five}', - '6': '\N{Subscript Six}', - '7': '\N{Subscript Seven}', - '8': '\N{Subscript Eight}', - '9': '\N{Subscript Nine}', - '+': '\N{Subscript Plus Sign}', - '=': '\N{Subscript Equals Sign}', - '(': '\N{Subscript Left Parenthesis}', - ')': '\N{Subscript Right Parenthesis}', - 'a': '\N{Latin Subscript Small Letter A}', - 'e': '\N{Latin Subscript Small Letter E}', - 'o': '\N{Latin Subscript Small Letter O}', - 'x': '\N{Latin Subscript Small Letter X}', - 'h': '\N{Latin Subscript Small Letter H}', - 'k': '\N{Latin Subscript Small Letter K}', - 'l': '\N{Latin Subscript Small Letter L}', - 'm': '\N{Latin Subscript Small Letter M}', - 'n': '\N{Latin Subscript Small Letter N}', - 'p': '\N{Latin Subscript Small Letter P}', - 's': '\N{Latin Subscript Small Letter S}', - 't': '\N{Latin Subscript Small Letter T}', - 'i': '\N{Latin Subscript Small Letter I}', - 'r': '\N{Latin Subscript Small Letter R}', - 'u': '\N{Latin Subscript Small Letter U}', - 'v': '\N{Latin Subscript Small Letter V}', - r'.': '.' + "-": "\N{SUBSCRIPT MINUS}", + "0": "\N{SUBSCRIPT ZERO}", + "1": "\N{SUBSCRIPT ONE}", + "2": "\N{SUBSCRIPT TWO}", + "3": "\N{SUBSCRIPT THREE}", + "4": "\N{SUBSCRIPT FOUR}", + "5": "\N{SUBSCRIPT FIVE}", + "6": "\N{SUBSCRIPT SIX}", + "7": "\N{SUBSCRIPT SEVEN}", + "8": "\N{SUBSCRIPT EIGHT}", + "9": "\N{SUBSCRIPT NINE}", + "+": "\N{SUBSCRIPT PLUS SIGN}", + "=": "\N{SUBSCRIPT EQUALS SIGN}", + "(": "\N{SUBSCRIPT LEFT PARENTHESIS}", + ")": "\N{SUBSCRIPT RIGHT PARENTHESIS}", + "a": "\N{LATIN SUBSCRIPT SMALL LETTER A}", + "e": "\N{LATIN SUBSCRIPT SMALL LETTER E}", + "o": "\N{LATIN SUBSCRIPT SMALL LETTER O}", + "x": "\N{LATIN SUBSCRIPT SMALL LETTER X}", + "h": "\N{LATIN SUBSCRIPT SMALL LETTER H}", + "k": "\N{LATIN SUBSCRIPT SMALL LETTER K}", + "l": "\N{LATIN SUBSCRIPT SMALL LETTER L}", + "m": "\N{LATIN SUBSCRIPT SMALL LETTER M}", + "n": "\N{LATIN SUBSCRIPT SMALL LETTER N}", + "p": "\N{LATIN SUBSCRIPT SMALL LETTER P}", + "s": "\N{LATIN SUBSCRIPT SMALL LETTER S}", + "t": "\N{LATIN SUBSCRIPT SMALL LETTER T}", + "i": "\N{LATIN SUBSCRIPT SMALL LETTER I}", + "r": "\N{LATIN SUBSCRIPT SMALL LETTER R}", + "u": "\N{LATIN SUBSCRIPT SMALL LETTER U}", + "v": "\N{LATIN SUBSCRIPT SMALL LETTER V}", + r".": ".", } -subscript_re = re.compile(r'\$_([' + ''.join(subscript.keys()) + r']+)\$') +subscript_re = re.compile(r"\$_([" + "".join(subscript.keys()) + r"]+)\$") # '-' must be first for the regex to work. superscript = { - '-': '\N{Superscript Minus}', - '0': '\N{Superscript Zero}', - '1': '\N{Superscript One}', - '2': '\N{Superscript Two}', - '3': '\N{Superscript Three}', - '4': '\N{Superscript Four}', - '5': '\N{Superscript Five}', - '6': '\N{Superscript Six}', - '7': '\N{Superscript Seven}', - '8': '\N{Superscript Eight}', - '9': '\N{Superscript Nine}', - '+': '\N{Superscript Plus Sign}', - '=': '\N{Superscript Equals Sign}', - '(': '\N{Superscript Left Parenthesis}', - ')': '\N{Superscript Right Parenthesis}', - 'a': '\N{Feminine Ordinal Indicator}', - 'b': 'ᵇ', - 'c': 'ᶜ', - 'd': 'ᵈ', - 'e': 'ᵉ', - 'f': 'ᶠ', - 'g': 'ᵍ', - 'h': 'ʰ', - 'i': 'ⁱ', - 'j': 'ʲ', - 'k': 'ᵏ', - 'l': 'ˡ', - 'm': 'ᵐ', - 'n': 'ⁿ', - 'o': '\N{Masculine Ordinal Indicator}', - 'p': 'ᵖ', - 'r': 'ʳ', - 's': 'ˢ', - 't': 'ᵗ', - 'u': 'ᵘ', - 'v': 'ᵛ', - 'w': 'ʷ', - 'x': 'ˣ', - 'y': 'ʸ', - 'z': 'ᶻ' + "-": "\N{SUPERSCRIPT MINUS}", + "0": "\N{SUPERSCRIPT ZERO}", + "1": "\N{SUPERSCRIPT ONE}", + "2": "\N{SUPERSCRIPT TWO}", + "3": "\N{SUPERSCRIPT THREE}", + "4": "\N{SUPERSCRIPT FOUR}", + "5": "\N{SUPERSCRIPT FIVE}", + "6": "\N{SUPERSCRIPT SIX}", + "7": "\N{SUPERSCRIPT SEVEN}", + "8": "\N{SUPERSCRIPT EIGHT}", + "9": "\N{SUPERSCRIPT NINE}", + "+": "\N{SUPERSCRIPT PLUS SIGN}", + "=": "\N{SUPERSCRIPT EQUALS SIGN}", + "(": "\N{SUPERSCRIPT LEFT PARENTHESIS}", + ")": "\N{SUPERSCRIPT RIGHT PARENTHESIS}", + "a": "\N{FEMININE ORDINAL INDICATOR}", + "b": "ᵇ", + "c": "ᶜ", + "d": "ᵈ", + "e": "ᵉ", + "f": "ᶠ", + "g": "ᵍ", + "h": "ʰ", + "i": "ⁱ", + "j": "ʲ", + "k": "ᵏ", + "l": "ˡ", + "m": "ᵐ", + "n": "ⁿ", + "o": "\N{MASCULINE ORDINAL INDICATOR}", + "p": "ᵖ", + "r": "ʳ", + "s": "ˢ", + "t": "ᵗ", + "u": "ᵘ", + "v": "ᵛ", + "w": "ʷ", + "x": "ˣ", + "y": "ʸ", + "z": "ᶻ", } -superscript_re = re.compile(r'\$\^([' + ''.join(superscript.keys()) + r']+)\$') +superscript_re = re.compile(r"\$\^([" + "".join(superscript.keys()) + r"]+)\$") greek_symbol = { - 'alpha': '\N{Greek Small Letter Alpha}', - 'beta': '\N{Greek Small Letter Beta}', - 'gamma': '\N{Greek Small Letter Gamma}', - 'delta': '\N{Greek Small Letter Delta}', - 'epsilon': '\N{Greek Small Letter Epsilon}', - 'zeta': '\N{Greek Small Letter Zeta}', - 'eta': '\N{Greek Small Letter Eta}', - 'theta': '\N{Greek Small Letter Theta}', - 'iota': '\N{Greek Small Letter Iota}', - 'kappa': '\N{Greek Small Letter Kappa}', - 'lamda': '\N{Greek Small Letter Lamda}', - 'lambda': '\N{Greek Small Letter Lamda}', - 'mu': '\N{Greek Small Letter Mu}', - 'nu': '\N{Greek Small Letter Nu}', - 'xi': '\N{Greek Small Letter Xi}', - 'omicron': '\N{Greek Small Letter Omicron}', - 'pi': '\N{Greek Small Letter Pi}', - 'rho': '\N{Greek Small Letter Rho}', - 'sigma': '\N{Greek Small Letter Sigma}', - 'tau': '\N{Greek Small Letter Tau}', - 'upsilon': '\N{Greek Small Letter Upsilon}', - 'phi': '\N{Greek Small Letter Phi}', - 'chi': '\N{Greek Small Letter Chi}', - 'psi': '\N{Greek Small Letter Psi}', - 'omega': '\N{Greek Small Letter Omega}', - 'Alpha': '\N{Greek Capital Letter Alpha}', - 'Beta': '\N{Greek Capital Letter Beta}', - 'Gamma': '\N{Greek Capital Letter Gamma}', - 'Delta': '\N{Greek Capital Letter Delta}', - 'Epsilon': '\N{Greek Capital Letter Epsilon}', - 'Zeta': '\N{Greek Capital Letter Zeta}', - 'Eta': '\N{Greek Capital Letter Eta}', - 'Theta': '\N{Greek Capital Letter Theta}', - 'Iota': '\N{Greek Capital Letter Iota}', - 'Kappa': '\N{Greek Capital Letter Kappa}', - 'Lamda': '\N{Greek Capital Letter Lamda}', - 'Lambda': '\N{Greek Capital Letter Lamda}', - 'Mu': '\N{Greek Capital Letter Mu}', - 'Nu': '\N{Greek Capital Letter Nu}', - 'Xi': '\N{Greek Capital Letter Xi}', - 'Omicron': '\N{Greek Capital Letter Omicron}', - 'Pi': '\N{Greek Capital Letter Pi}', - 'Rho': '\N{Greek Capital Letter Rho}', - 'Sigma': '\N{Greek Capital Letter Sigma}', - 'Tau': '\N{Greek Capital Letter Tau}', - 'Upsilon': '\N{Greek Capital Letter Upsilon}', - 'Phi': '\N{Greek Capital Letter Phi}', - 'Chi': '\N{Greek Capital Letter Chi}', - 'Psi': '\N{Greek Capital Letter Psi}', - 'Omega': '\N{Greek Capital Letter Omega}', + "alpha": "\N{GREEK SMALL LETTER ALPHA}", + "beta": "\N{GREEK SMALL LETTER BETA}", + "gamma": "\N{GREEK SMALL LETTER GAMMA}", + "delta": "\N{GREEK SMALL LETTER DELTA}", + "epsilon": "\N{GREEK SMALL LETTER EPSILON}", + "zeta": "\N{GREEK SMALL LETTER ZETA}", + "eta": "\N{GREEK SMALL LETTER ETA}", + "theta": "\N{GREEK SMALL LETTER THETA}", + "iota": "\N{GREEK SMALL LETTER IOTA}", + "kappa": "\N{GREEK SMALL LETTER KAPPA}", + "lamda": "\N{GREEK SMALL LETTER LAMDA}", + "lambda": "\N{GREEK SMALL LETTER LAMDA}", + "mu": "\N{GREEK SMALL LETTER MU}", + "nu": "\N{GREEK SMALL LETTER NU}", + "xi": "\N{GREEK SMALL LETTER XI}", + "omicron": "\N{GREEK SMALL LETTER OMICRON}", + "pi": "\N{GREEK SMALL LETTER PI}", + "rho": "\N{GREEK SMALL LETTER RHO}", + "sigma": "\N{GREEK SMALL LETTER SIGMA}", + "tau": "\N{GREEK SMALL LETTER TAU}", + "upsilon": "\N{GREEK SMALL LETTER UPSILON}", + "phi": "\N{GREEK SMALL LETTER PHI}", + "chi": "\N{GREEK SMALL LETTER CHI}", + "psi": "\N{GREEK SMALL LETTER PSI}", + "omega": "\N{GREEK SMALL LETTER OMEGA}", + "Alpha": "\N{GREEK CAPITAL LETTER ALPHA}", + "Beta": "\N{GREEK CAPITAL LETTER BETA}", + "Gamma": "\N{GREEK CAPITAL LETTER GAMMA}", + "Delta": "\N{GREEK CAPITAL LETTER DELTA}", + "Epsilon": "\N{GREEK CAPITAL LETTER EPSILON}", + "Zeta": "\N{GREEK CAPITAL LETTER ZETA}", + "Eta": "\N{GREEK CAPITAL LETTER ETA}", + "Theta": "\N{GREEK CAPITAL LETTER THETA}", + "Iota": "\N{GREEK CAPITAL LETTER IOTA}", + "Kappa": "\N{GREEK CAPITAL LETTER KAPPA}", + "Lamda": "\N{GREEK CAPITAL LETTER LAMDA}", + "Lambda": "\N{GREEK CAPITAL LETTER LAMDA}", + "Mu": "\N{GREEK CAPITAL LETTER MU}", + "Nu": "\N{GREEK CAPITAL LETTER NU}", + "Xi": "\N{GREEK CAPITAL LETTER XI}", + "Omicron": "\N{GREEK CAPITAL LETTER OMICRON}", + "Pi": "\N{GREEK CAPITAL LETTER PI}", + "Rho": "\N{GREEK CAPITAL LETTER RHO}", + "Sigma": "\N{GREEK CAPITAL LETTER SIGMA}", + "Tau": "\N{GREEK CAPITAL LETTER TAU}", + "Upsilon": "\N{GREEK CAPITAL LETTER UPSILON}", + "Phi": "\N{GREEK CAPITAL LETTER PHI}", + "Chi": "\N{GREEK CAPITAL LETTER CHI}", + "Psi": "\N{GREEK CAPITAL LETTER PSI}", + "Omega": "\N{GREEK CAPITAL LETTER OMEGA}", } -greek_symbol_re = re.compile(r'\$\\(' + '|'.join(greek_symbol.keys()) + r')\$') +greek_symbol_re = re.compile(r"\$\\(" + "|".join(greek_symbol.keys()) + r")\$") class Reference_Handler(object): @@ -173,7 +173,7 @@ def __del__(self): pass # print('Database was already closed.') - def dump(self, outfile=None, fmt='bibtex', level=3): + def dump(self, outfile=None, fmt="bibtex", level=3): """ Retrieves the individual citations that were collected during the execution of a program and tallies the number of times each citation @@ -199,15 +199,14 @@ def dump(self, outfile=None, fmt='bibtex', level=3): """ if fmt not in supported_fmts: - raise NameError('Format %s not currently supported.' % (fmt)) + raise NameError("Format %s not currently supported." % (fmt)) if fmt not in supported_fmts: - raise NameError('Format %s not currently supported.' % (fmt)) + raise NameError("Format %s not currently supported." % (fmt)) if level not in range(1, 4) and level is not None: raise ValueError( - 'Invalid value for level. Please input a value in the range ' - '[1,3]' + "Invalid value for level. Please input a value in the range [1,3]" ) self.cur.execute( @@ -220,46 +219,49 @@ def dump(self, outfile=None, fmt='bibtex', level=3): GROUP BY reference_id ) t2 ON t1.id = t2.reference_id WHERE counts > 0 ORDER BY counts DESC - """, (level,) + """, + (level,), ) query = self.cur.fetchall() - if fmt == 'bibtex': + if fmt == "bibtex": ret = query if outfile is not None: if type(outfile) is not str: raise TypeError( - 'The name of the output file must be a string but it ' - 'is %s' % type(outfile) + "The name of the output file must be a string but it " + "is %s" % type(outfile) ) - with open(outfile, 'w') as f: + with open(outfile, "w") as f: for item in query: - f.write('TOTAL_MENTIONS: %s \n' % str(item[2])) - f.write('LEVEL: %s \n' % str(item[3])) + f.write("TOTAL_MENTIONS: %s \n" % str(item[2])) + f.write("LEVEL: %s \n" % str(item[3])) f.write(item[1]) - elif fmt == 'text': + elif fmt == "text": ret = [] for item in query: parse = bibtexparser.loads(item[1]).entries[0] - entry_type = parse['ENTRYTYPE'] - if entry_type == 'misc': + entry_type = parse["ENTRYTYPE"] + if entry_type == "misc": plain_text = self.format_misc(parse) - elif entry_type == 'article': + elif entry_type == "software": + plain_text = self.format_software(parse) + elif entry_type == "article": plain_text = self.format_article(parse) - elif entry_type == 'inbook': + elif entry_type == "inbook": plain_text = self.format_inbook(parse) - elif entry_type == 'phdthesis': + elif entry_type == "phdthesis": plain_text = self.format_phdthesis(parse) else: plain_text = f"Do not have a handler for '{entry_type}':" - plain_text += '\n' + plain_text += "\n" plain_text += pprint.pformat(parse) plain_text = decode_latex(plain_text) @@ -269,7 +271,7 @@ def dump(self, outfile=None, fmt='bibtex', level=3): return ret @staticmethod - def load_bibliography(bibfile=None, fmt='bibtex'): + def load_bibliography(bibfile=None, fmt="bibtex"): """ Utility function to read a bibliographic file in common formats. The current supported formats are BibTeX. @@ -293,19 +295,19 @@ def load_bibliography(bibfile=None, fmt='bibtex'): """ if bibfile is None: - raise FileNotFoundError('A bibliography file must be specified.') + raise FileNotFoundError("A bibliography file must be specified.") if fmt not in supported_fmts: - raise NameError('Format %s not currently supported.' % (fmt)) + raise NameError("Format %s not currently supported." % (fmt)) - with open(bibfile, 'r') as f: + with open(bibfile, "r") as f: parser = bibtexparser.bparser.BibTexParser(common_strings=True) bibliography = bibtexparser.load(f, parser=parser).entries - ret = {k['ID']: {} for k in bibliography} + ret = {k["ID"]: {} for k in bibliography} for entry in bibliography: - ret[entry['ID']] = entry_to_bibtex(entry) + ret[entry["ID"]] = entry_to_bibtex(entry) return ret @@ -316,8 +318,8 @@ def cite( module=None, level=1, note=None, - fmt='bibtex', - doi=None + fmt="bibtex", + doi=None, ): """ Adds a given reference to the internal database. @@ -353,8 +355,7 @@ def cite( if alias is None or raw is None or module is None or note is None: raise NameError( - 'Need to provide the "alias", "raw", "module" and "note" ' - 'arguments' + 'Need to provide the "alias", "raw", "module" and "note" arguments' ) doi = self._extract_doi(raw, fmt) @@ -365,25 +366,16 @@ def cite( self._create_citation(raw=raw, alias=alias, doi=doi) reference_id = self.cur.lastrowid self._create_context( - reference_id=reference_id, - module=module, - note=note, - level=level + reference_id=reference_id, module=module, note=note, level=level ) else: context_id = self._get_context_id( - reference_id=reference_id, - module=module, - note=note, - level=level + reference_id=reference_id, module=module, note=note, level=level ) if context_id is None: self._create_context( - reference_id=reference_id, - module=module, - note=note, - level=level + reference_id=reference_id, module=module, note=note, level=level ) else: self._update_counter(context_id=context_id) @@ -404,47 +396,38 @@ def _update_counter(self, context_id=None): "UPDATE context SET count = count + 1 WHERE id=?;", (context_id,) ) - def _extract_doi(self, raw=None, fmt='bibtex'): + def _extract_doi(self, raw=None, fmt="bibtex"): """ Parses DOI from bibliographic format """ if fmt not in supported_fmts: - raise NameError('Format %s not currently supported.' % (fmt)) + raise NameError("Format %s not currently supported." % (fmt)) - if fmt == 'bibtex': + if fmt == "bibtex": ret = bibtexparser.loads(raw) ret = ret.entries[0] - if 'doi' in ret.keys(): - return ret['doi'] + if "doi" in ret.keys(): + return ret["doi"] def _initialize_tables(self): """ Initializes the citation and context tables """ - self.cur.execute( - """CREATE TABLE IF NOT EXISTS "citation" ( + self.cur.execute("""CREATE TABLE IF NOT EXISTS "citation" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "alias" TEXT NOT NULL UNIQUE, "raw" TEXT NOT NULL UNIQUE, "doi" TEXT UNIQUE ); - """ - ) + """) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_raw on citation (raw);" - ) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_alias on citation (alias);" - ) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_doi on citation (doi);" - ) + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_raw on citation (raw);") + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_alias on citation (alias);") + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_doi on citation (doi);") - self.cur.execute( - """ + self.cur.execute(""" CREATE TABLE IF NOT EXISTS "context" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "reference_id" INTEGER NOT NULL, @@ -454,21 +437,14 @@ def _initialize_tables(self): "level" INTEGER NOT NULL, FOREIGN KEY(reference_id) REFERENCES Citation(id) ); - """ - ) + """) self.cur.execute( "CREATE INDEX IF NOT EXISTS idx_refid on context (reference_id);" ) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_module on context (module);" - ) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_count on context (count);" - ) - self.cur.execute( - "CREATE INDEX IF NOT EXISTS idx_level on context (level);" - ) + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_module on context (module);") + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_count on context (count);") + self.cur.execute("CREATE INDEX IF NOT EXISTS idx_level on context (level);") self.conn.commit() @@ -484,13 +460,9 @@ def _get_reference_id(self, raw=None, alias=None, doi=None): 'Variables "raw" or "alias" or "DOI" must be input.' ) else: - self.cur.execute( - "SELECT id FROM citation WHERE doi=?;" (doi,) - ) + self.cur.execute("SELECT id FROM citation WHERE doi=?;", (doi,)) else: - self.cur.execute( - "SELECT id FROM citation WHERE alias=?;" (alias,) - ) + self.cur.execute("SELECT id FROM citation WHERE alias=?;", (alias,)) else: self.cur.execute("SELECT id FROM citation WHERE raw=?;", (raw,)) @@ -501,18 +473,13 @@ def _get_reference_id(self, raw=None, alias=None, doi=None): return ret[0][0] - def _get_context_id( - self, reference_id=None, module=None, note=None, level=None - ): + def _get_context_id(self, reference_id=None, module=None, note=None, level=None): """ Gets the ID of the context if exists. A context is specified by (reference_id, module, note, level) combination """ - if ( - reference_id is None or module is None or note is None or - level is None - ): + if reference_id is None or module is None or note is None or level is None: raise NameError( 'The variables "reference_id" and "module" and "note" and ' '"level" must be specified' @@ -520,7 +487,8 @@ def _get_context_id( self.cur.execute( "SELECT id FROM context WHERE reference_id=? AND module=? AND " - "note=? AND level=?;", (reference_id, module, note, level) + "note=? AND level=?;", + (reference_id, module, note, level), ) ret = self.cur.fetchall() @@ -536,18 +504,16 @@ def _create_citation(self, raw=None, alias=None, doi=None): """ if raw is None or alias is None: - raise NameError('The value for raw and alias must be provided') + raise NameError("The value for raw and alias must be provided") else: self.cur.execute( "INSERT INTO citation (raw, alias, doi) VALUES (?, ?, ?);", - (raw, alias, doi) + (raw, alias, doi), ) self.conn.commit() - def _create_context( - self, reference_id=None, module=None, note=None, level=None - ): + def _create_context(self, reference_id=None, module=None, note=None, level=None): """ Adds a new record to the context table using the combination of the provided arguments. @@ -558,7 +524,8 @@ def _create_context( self.cur.execute( "INSERT INTO context (reference_id, module, note, count, level) " - "VALUES (?, ?, ?, ?, ?)", (reference_id, module, note, 1, level) + "VALUES (?, ?, ?, ?, ?)", + (reference_id, module, note, 1, level), ) self.conn.commit() @@ -569,9 +536,7 @@ def total_mentions(self, reference_id=None, alias=None): """ if reference_id is None: if alias is None: - raise NameError( - "The 'reference_id' or 'alias' must be provided." - ) + raise NameError("The 'reference_id' or 'alias' must be provided.") else: self.cur.execute( """ @@ -583,7 +548,8 @@ def total_mentions(self, reference_id=None, alias=None): ) t2 ON t1.id = t2.reference_id WHERE alias = ? - """, (alias,) + """, + (alias,), ) else: @@ -597,7 +563,8 @@ def total_mentions(self, reference_id=None, alias=None): ) t2 ON t1.id = t2.reference_id WHERE id = ? - """, (reference_id,) + """, + (reference_id,), ) ret = self.cur.fetchall() @@ -658,13 +625,13 @@ def total_contexts(self, reference_id=None, alias=None): SELECT id, reference_id FROM context ) t2 ON citation.id = t2.reference_id WHERE alias=? - """, (alias,) + """, + (alias,), ) else: self.cur.execute( - "SELECT COUNT(*) FROM context WHERE reference_id = ?;", - (reference_id,) + "SELECT COUNT(*) FROM context WHERE reference_id = ?;", (reference_id,) ) return self.cur.fetchall()[0][0] @@ -683,22 +650,22 @@ def format_article(self, data): DOI: 10.1021/jacs.8b08648 """ - result = '' - if 'author' in data: - result += '; '.join(data['author'].split(' and ')) - if result[-1] != '.': - result += '.' - if 'title' in data: - result += ' ' + data['title'].rstrip('.') + '.' - if 'journal' in data: - result += ' ' + data['journal'] - if 'year' in data: + result = "" + if "author" in data: + result += "; ".join(data["author"].split(" and ")) + if result[-1] != ".": + result += "." + if "title" in data: + result += " " + data["title"].rstrip(".") + "." + if "journal" in data: + result += " " + data["journal"] + if "year" in data: result += f" {data['year']}," - if 'volume' in data: + if "volume" in data: result += f" {data['volume']}," - if 'pages' in data: + if "pages" in data: result += f" {data['pages']}." - if 'doi' in data: + if "doi" in data: result += f" DOI: {data['doi']}" return result @@ -715,23 +682,23 @@ def format_phdthesis(self, data): (accessed 2019-09-10). """ - result = '' - if 'author' in data: - result += '; '.join(data['author'].split(' and ')) - if result[-1] != '.': - result += '.' - if 'title' in data: - result += ' ' + data['title'].rstrip('.') + '.' + result = "" + if "author" in data: + result += "; ".join(data["author"].split(" and ")) + if result[-1] != ".": + result += "." + if "title" in data: + result += " " + data["title"].rstrip(".") + "." result += " Ph.D. Dissertation" - if 'school' in data: + if "school" in data: result += f", {data['school']}" - if 'address' in data: + if "address" in data: result += f", {data['address']}" - if 'year' in data: + if "year" in data: result += f", {data['year']}." - if 'url' in data: + if "url" in data: result += f", {data['url']}" - if 'doi' in data: + if "doi" in data: result += f", DOI: {data['doi']}" return result @@ -747,30 +714,34 @@ def format_misc(self, data): University: Pittsburgh, PA, 1982. """ - result = '' + result = "" - if 'author' in data: - result += '; '.join(data['author'].split(' and ')) - if result[-1] != '.': - result += '.' - if 'title' in data: - result += ' ' + data['title'] - if 'version' in data: + if "author" in data: + result += "; ".join(data["author"].split(" and ")) + if result[-1] != ".": + result += "." + result += " " + if "title" in data: + result += data["title"] + if "version" in data: result += f", version {data['version']};" else: - result += ';' + result += ";" - if 'organization' in data: - result += ' ' + data['organization'] - if 'address' in data: + if "organization" in data: + result += " " + data["organization"] + if "address" in data: result += f": {data['address']}" - if 'url' in data: + if "url" in data: result += f", {data['url']}" - if 'doi' in data: + if "doi" in data: result += f", DOI: {data['doi']}" return result + def format_software(self, data): + return self.format_misc(self, data) + def format_inbook(self, data): """Format a chapter or part of a book or series BibTex record @@ -786,26 +757,26 @@ def format_inbook(self, data): 2018; pp 33−44. DOI: 10.1021/bk-2018-1295.ch003 """ - result = '' - if 'author' in data: - result += '; '.join(data['author'].split(' and ')) - if result[-1] != '.': - result += '.' - if 'title' in data: - result += ' ' + data['title'].rstrip('.') + '. In' - if 'booktitle' in data: + result = "" + if "author" in data: + result += "; ".join(data["author"].split(" and ")) + if result[-1] != ".": + result += "." + if "title" in data: + result += " " + data["title"].rstrip(".") + ". In" + if "booktitle" in data: result += f" {data['booktitle']}" - if 'series' in data: + if "series" in data: result += f"; {data['series']}" - if 'publisher' in data: + if "publisher" in data: result += f"; {data['publisher']}" - if 'place' in data: + if "place" in data: result += f", {data['place']}" - if 'year' in data: + if "year" in data: result += f", {data['year']}." - if 'url' in data: + if "url" in data: result += f", {data['url']}" - if 'doi' in data: + if "doi" in data: result += f", DOI: {data['doi']}" return result @@ -817,13 +788,13 @@ def decode_math_symbols(self, text): return subscript_re.sub(self._decode_subscript, text) def _decode_subscript(self, match): - result = '' + result = "" for digit in list(match[1]): result += subscript[digit] return result def _decode_superscript(self, match): - result = '' + result = "" for digit in list(match[1]): result += superscript[digit] return result diff --git a/reference_handler/tests/test_exceptions.py b/reference_handler/tests/test_exceptions.py index 81cfe29..c9e7a26 100644 --- a/reference_handler/tests/test_exceptions.py +++ b/reference_handler/tests/test_exceptions.py @@ -9,7 +9,7 @@ from . import build_filenames from inspect import signature -database = build_filenames.build_data_filename('database.db') +database = build_filenames.build_data_filename("database.db") lammps_citation = """ @article{PLIMPTON19951, @@ -47,10 +47,8 @@ def _create_db(): """Boiler plate""" - database_name = 'tmp.db' - database = build_filenames.build_scratch_filename( - database_name - ) # noqa: F821 + database_name = "tmp.db" + database = build_filenames.build_scratch_filename(database_name) # noqa: F821 if os.path.exists(database): os.remove(database) @@ -58,20 +56,19 @@ def _create_db(): @pytest.fixture( - scope='function', + scope="function", params=[ - x for x in range( - len( - signature(reference_handler.Reference_Handler.cite).parameters - ) - 1 + x + for x in range( + len(signature(reference_handler.Reference_Handler.cite).parameters) - 1 ) - ] + ], ) def create_test_arg(request): test_arg = [None] * ( len(signature(reference_handler.Reference_Handler.cite).parameters) - 1 ) - test_arg[request.param] = 'string' + test_arg[request.param] = "string" yield tuple(test_arg) diff --git a/reference_handler/tests/test_latex.py b/reference_handler/tests/test_latex.py index 14af3ea..943c9c7 100644 --- a/reference_handler/tests/test_latex.py +++ b/reference_handler/tests/test_latex.py @@ -8,378 +8,378 @@ from reference_handler import encode_latex import sys -alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" def test_latex_utf8_imported(): """Sample test, will always pass so long as import statement worked""" - assert 'reference_handler' in sys.modules + assert "reference_handler" in sys.modules def test_latex_umlauts(): """LaTex command for an umlaut on a character, e.g. \"{a}""" - answer = 'ÄB̈C̈D̈ËF̈G̈ḦÏJ̈K̈L̈M̈N̈ÖP̈Q̈R̈S̈T̈ÜV̈ẄẌŸZ̈äb̈c̈d̈ëf̈g̈ḧïj̈k̈l̈m̈n̈öp̈q̈r̈s̈ẗüv̈ẅẍÿz̈ı̈ȷ̈' # noqa: E501 + answer = "ÄB̈C̈D̈ËF̈G̈ḦÏJ̈K̈L̈M̈N̈ÖP̈Q̈R̈S̈T̈ÜV̈ẄẌŸZ̈äb̈c̈d̈ëf̈g̈ḧïj̈k̈l̈m̈n̈öp̈q̈r̈s̈ẗüv̈ẅẍÿz̈ı̈ȷ̈" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\"{' + char + '}' - text += r'\"{\i}' # dotless i - text += r'\"{\j}' # dotless j + text += r"\"{" + char + "}" + text += r"\"{\i}" # dotless i + text += r"\"{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_acute_accents(): """LaTex command for an acute accent character, e.g. \'{a}""" - answer = 'ÁB́ĆD́ÉF́ǴH́ÍJ́ḰĹḾŃÓṔQ́ŔŚT́ÚV́ẂX́ÝŹáb́ćd́éf́ǵh́íj́ḱĺḿńóṕq́ŕśt́úv́ẃx́ýźı́ȷ́' # noqa: E501 + answer = "ÁB́ĆD́ÉF́ǴH́ÍJ́ḰĹḾŃÓṔQ́ŔŚT́ÚV́ẂX́ÝŹáb́ćd́éf́ǵh́íj́ḱĺḿńóṕq́ŕśt́úv́ẃx́ýźı́ȷ́" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r"\'{" + char + '}' + text += r"\'{" + char + "}" text += r"\'{\i}" # dotless i text += r"\'{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_dot_above(): r"""LaTex command for a dot above the character, e.g. \.{a}""" - answer = 'ȦḂĊḊĖḞĠḢİJ̇K̇L̇ṀṄȮṖQ̇ṘṠṪU̇V̇ẆẊẎŻȧḃċḋėḟġḣi̇j̇k̇l̇ṁṅȯṗq̇ṙṡṫu̇v̇ẇẋẏżı̇ȷ̇' # noqa: E501 + answer = "ȦḂĊḊĖḞĠḢİJ̇K̇L̇ṀṄȮṖQ̇ṘṠṪU̇V̇ẆẊẎŻȧḃċḋėḟġḣi̇j̇k̇l̇ṁṅȯṗq̇ṙṡṫu̇v̇ẇẋẏżı̇ȷ̇" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\.{' + char + '}' - text += r'\.{\i}' # dotless i - text += r'\.{\j}' # dotless j + text += r"\.{" + char + "}" + text += r"\.{\i}" # dotless i + text += r"\.{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_macron_above(): r"""LaTex command for a macron above the character, e.g. \={a}""" - answer = 'ĀB̄C̄D̄ĒF̄ḠH̄ĪJ̄K̄L̄M̄N̄ŌP̄Q̄R̄S̄T̄ŪV̄W̄X̄ȲZ̄āb̄c̄d̄ēf̄ḡh̄īj̄k̄l̄m̄n̄ōp̄q̄r̄s̄t̄ūv̄w̄x̄ȳz̄ı̄ȷ̄' # noqa: E501 + answer = "ĀB̄C̄D̄ĒF̄ḠH̄ĪJ̄K̄L̄M̄N̄ŌP̄Q̄R̄S̄T̄ŪV̄W̄X̄ȲZ̄āb̄c̄d̄ēf̄ḡh̄īj̄k̄l̄m̄n̄ōp̄q̄r̄s̄t̄ūv̄w̄x̄ȳz̄ı̄ȷ̄" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\={' + char + '}' - text += r'\={\i}' # dotless i - text += r'\={\j}' # dotless j + text += r"\={" + char + "}" + text += r"\={\i}" # dotless i + text += r"\={\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_circumflex_above(): r"""LaTex command for a circumflex above the character, e.g. \^{a}""" - answer = 'ÂB̂ĈD̂ÊF̂ĜĤÎĴK̂L̂M̂N̂ÔP̂Q̂R̂ŜT̂ÛV̂ŴX̂ŶẐâb̂ĉd̂êf̂ĝĥîĵk̂l̂m̂n̂ôp̂q̂r̂ŝt̂ûv̂ŵx̂ŷẑı̂ȷ̂' # noqa: E501 + answer = "ÂB̂ĈD̂ÊF̂ĜĤÎĴK̂L̂M̂N̂ÔP̂Q̂R̂ŜT̂ÛV̂ŴX̂ŶẐâb̂ĉd̂êf̂ĝĥîĵk̂l̂m̂n̂ôp̂q̂r̂ŝt̂ûv̂ŵx̂ŷẑı̂ȷ̂" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\^{' + char + '}' - text += r'\^{\i}' # dotless i - text += r'\^{\j}' # dotless j + text += r"\^{" + char + "}" + text += r"\^{\i}" # dotless i + text += r"\^{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_grave_accent(): r"""LaTex command for a grave accent on the character, e.g. \`{a}""" - answer = 'ÀB̀C̀D̀ÈF̀G̀H̀ÌJ̀K̀L̀M̀ǸÒP̀Q̀R̀S̀T̀ÙV̀ẀX̀ỲZ̀àb̀c̀d̀èf̀g̀h̀ìj̀k̀l̀m̀ǹòp̀q̀r̀s̀t̀ùv̀ẁx̀ỳz̀ı̀ȷ̀' # noqa: E501 + answer = "ÀB̀C̀D̀ÈF̀G̀H̀ÌJ̀K̀L̀M̀ǸÒP̀Q̀R̀S̀T̀ÙV̀ẀX̀ỲZ̀àb̀c̀d̀èf̀g̀h̀ìj̀k̀l̀m̀ǹòp̀q̀r̀s̀t̀ùv̀ẁx̀ỳz̀ı̀ȷ̀" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\`{' + char + '}' - text += r'\`{\i}' # dotless i - text += r'\`{\j}' # dotless j + text += r"\`{" + char + "}" + text += r"\`{\i}" # dotless i + text += r"\`{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_vertical_line_above(): r"""LaTex command for a vertical line above the character, e.g. \|{a}""" - answer = 'A̍B̍C̍D̍E̍F̍G̍H̍I̍J̍K̍L̍M̍N̍O̍P̍Q̍R̍S̍T̍U̍V̍W̍X̍Y̍Z̍a̍b̍c̍d̍e̍f̍g̍h̍i̍j̍k̍l̍m̍n̍o̍p̍q̍r̍s̍t̍u̍v̍w̍x̍y̍z̍ı̍ȷ̍' # noqa: E501 + answer = "A̍B̍C̍D̍E̍F̍G̍H̍I̍J̍K̍L̍M̍N̍O̍P̍Q̍R̍S̍T̍U̍V̍W̍X̍Y̍Z̍a̍b̍c̍d̍e̍f̍g̍h̍i̍j̍k̍l̍m̍n̍o̍p̍q̍r̍s̍t̍u̍v̍w̍x̍y̍z̍ı̍ȷ̍" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\|{' + char + '}' - text += r'\|{\i}' # dotless i - text += r'\|{\j}' # dotless j + text += r"\|{" + char + "}" + text += r"\|{\i}" # dotless i + text += r"\|{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_tilde_above(): r"""LaTex command for a tilde above the character, e.g. \~{a}""" - answer = 'ÃB̃C̃D̃ẼF̃G̃H̃ĨJ̃K̃L̃M̃ÑÕP̃Q̃R̃S̃T̃ŨṼW̃X̃ỸZ̃ãb̃c̃d̃ẽf̃g̃h̃ĩj̃k̃l̃m̃ñõp̃q̃r̃s̃t̃ũṽw̃x̃ỹz̃ı̃ȷ̃' # noqa: E501 + answer = "ÃB̃C̃D̃ẼF̃G̃H̃ĨJ̃K̃L̃M̃ÑÕP̃Q̃R̃S̃T̃ŨṼW̃X̃ỸZ̃ãb̃c̃d̃ẽf̃g̃h̃ĩj̃k̃l̃m̃ñõp̃q̃r̃s̃t̃ũṽw̃x̃ỹz̃ı̃ȷ̃" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\~{' + char + '}' - text += r'\~{\i}' # dotless i - text += r'\~{\j}' # dotless j + text += r"\~{" + char + "}" + text += r"\~{\i}" # dotless i + text += r"\~{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_macron_below(): r"""LaTex command for a macron below the character, e.g. \b{a}""" - answer = 'A̱ḆC̱ḎE̱F̱G̱H̱I̱J̱ḴḺM̱ṈO̱P̱Q̱ṞS̱ṮU̱V̱W̱X̱Y̱Ẕa̱ḇc̱ḏe̱f̱g̱ẖi̱j̱ḵḻm̱ṉo̱p̱q̱ṟs̱ṯu̱v̱w̱x̱y̱ẕı̱ȷ̱' # noqa: E501 + answer = "A̱ḆC̱ḎE̱F̱G̱H̱I̱J̱ḴḺM̱ṈO̱P̱Q̱ṞS̱ṮU̱V̱W̱X̱Y̱Ẕa̱ḇc̱ḏe̱f̱g̱ẖi̱j̱ḵḻm̱ṉo̱p̱q̱ṟs̱ṯu̱v̱w̱x̱y̱ẕı̱ȷ̱" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\b{' + char + '}' - text += r'\b{\i}' # dotless i - text += r'\b{\j}' # dotless j + text += r"\b{" + char + "}" + text += r"\b{\i}" # dotless i + text += r"\b{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_cedilla(): r"""LaTex command for a cedilla on the character, e.g. \c{a}""" - answer = 'A̧B̧ÇḐȨF̧ĢḨI̧J̧ĶĻM̧ŅO̧P̧Q̧ŖŞŢU̧V̧W̧X̧Y̧Z̧a̧b̧çḑȩf̧ģḩi̧j̧ķļm̧ņo̧p̧q̧ŗşţu̧v̧w̧x̧y̧z̧ı̧ȷ̧' # noqa: E501 + answer = "A̧B̧ÇḐȨF̧ĢḨI̧J̧ĶĻM̧ŅO̧P̧Q̧ŖŞŢU̧V̧W̧X̧Y̧Z̧a̧b̧çḑȩf̧ģḩi̧j̧ķļm̧ņo̧p̧q̧ŗşţu̧v̧w̧x̧y̧z̧ı̧ȷ̧" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\c{' + char + '}' - text += r'\c{\i}' # dotless i - text += r'\c{\j}' # dotless j + text += r"\c{" + char + "}" + text += r"\c{\i}" # dotless i + text += r"\c{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_double_grave_accent(): r"""LaTex command for a double grave accent on the character, e.g. \C{a}""" - answer = 'ȀB̏C̏D̏ȄF̏G̏H̏ȈJ̏K̏L̏M̏N̏ȌP̏Q̏ȐS̏T̏ȔV̏W̏X̏Y̏Z̏ȁb̏c̏d̏ȅf̏g̏h̏ȉj̏k̏l̏m̏n̏ȍp̏q̏ȑs̏t̏ȕv̏w̏x̏y̏z̏ı̏ȷ̏' # noqa: E501 + answer = "ȀB̏C̏D̏ȄF̏G̏H̏ȈJ̏K̏L̏M̏N̏ȌP̏Q̏ȐS̏T̏ȔV̏W̏X̏Y̏Z̏ȁb̏c̏d̏ȅf̏g̏h̏ȉj̏k̏l̏m̏n̏ȍp̏q̏ȑs̏t̏ȕv̏w̏x̏y̏z̏ı̏ȷ̏" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\C{' + char + '}' - text += r'\C{\i}' # dotless i - text += r'\C{\j}' # dotless j + text += r"\C{" + char + "}" + text += r"\C{\i}" # dotless i + text += r"\C{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_dot_below(): r"""LaTex command for a dot below the character, e.g. \d{a}""" - answer = 'ẠḄC̣ḌẸF̣G̣ḤỊJ̣ḲḶṂṆỌP̣Q̣ṚṢṬỤṾẈX̣ỴẒạḅc̣ḍẹf̣g̣ḥịj̣ḳḷṃṇọp̣q̣ṛṣṭụṿẉx̣ỵẓı̣ȷ̣' # noqa: E501 + answer = "ẠḄC̣ḌẸF̣G̣ḤỊJ̣ḲḶṂṆỌP̣Q̣ṚṢṬỤṾẈX̣ỴẒạḅc̣ḍẹf̣g̣ḥịj̣ḳḷṃṇọp̣q̣ṛṣṭụṿẉx̣ỵẓı̣ȷ̣" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\d{' + char + '}' - text += r'\d{\i}' # dotless i - text += r'\d{\j}' # dotless j + text += r"\d{" + char + "}" + text += r"\d{\i}" # dotless i + text += r"\d{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_inverted_breve(): r"""LaTex command for an inverted breve above the character, e.g. \f{a}""" - answer = 'ȂB̑C̑D̑ȆF̑G̑H̑ȊJ̑K̑L̑M̑N̑ȎP̑Q̑ȒS̑T̑ȖV̑W̑X̑Y̑Z̑ȃb̑c̑d̑ȇf̑g̑h̑ȋj̑k̑l̑m̑n̑ȏp̑q̑ȓs̑t̑ȗv̑w̑x̑y̑z̑ı̑ȷ̑' # noqa: E501 + answer = "ȂB̑C̑D̑ȆF̑G̑H̑ȊJ̑K̑L̑M̑N̑ȎP̑Q̑ȒS̑T̑ȖV̑W̑X̑Y̑Z̑ȃb̑c̑d̑ȇf̑g̑h̑ȋj̑k̑l̑m̑n̑ȏp̑q̑ȓs̑t̑ȗv̑w̑x̑y̑z̑ı̑ȷ̑" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\f{' + char + '}' - text += r'\f{\i}' # dotless i - text += r'\f{\j}' # dotless j + text += r"\f{" + char + "}" + text += r"\f{\i}" # dotless i + text += r"\f{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_hook_above(): r"""LaTex command for a hook above the character, e.g. \h{a}""" - answer = 'ẢB̉C̉D̉ẺF̉G̉H̉ỈJ̉K̉L̉M̉N̉ỎP̉Q̉R̉S̉T̉ỦV̉W̉X̉ỶZ̉ảb̉c̉d̉ẻf̉g̉h̉ỉj̉k̉l̉m̉n̉ỏp̉q̉r̉s̉t̉ủv̉w̉x̉ỷz̉ı̉ȷ̉' # noqa: E501 + answer = "ẢB̉C̉D̉ẺF̉G̉H̉ỈJ̉K̉L̉M̉N̉ỎP̉Q̉R̉S̉T̉ỦV̉W̉X̉ỶZ̉ảb̉c̉d̉ẻf̉g̉h̉ỉj̉k̉l̉m̉n̉ỏp̉q̉r̉s̉t̉ủv̉w̉x̉ỷz̉ı̉ȷ̉" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\h{' + char + '}' - text += r'\h{\i}' # dotless i - text += r'\h{\j}' # dotless j + text += r"\h{" + char + "}" + text += r"\h{\i}" # dotless i + text += r"\h{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_double_acute_accent(): r"""LaTex command for a double acute accent on the character, e.g. \H{a}""" - answer = 'A̋B̋C̋D̋E̋F̋G̋H̋I̋J̋K̋L̋M̋N̋ŐP̋Q̋R̋S̋T̋ŰV̋W̋X̋Y̋Z̋a̋b̋c̋d̋e̋f̋g̋h̋i̋j̋k̋l̋m̋n̋őp̋q̋r̋s̋t̋űv̋w̋x̋y̋z̋ı̋ȷ̋' # noqa: E501 + answer = "A̋B̋C̋D̋E̋F̋G̋H̋I̋J̋K̋L̋M̋N̋ŐP̋Q̋R̋S̋T̋ŰV̋W̋X̋Y̋Z̋a̋b̋c̋d̋e̋f̋g̋h̋i̋j̋k̋l̋m̋n̋őp̋q̋r̋s̋t̋űv̋w̋x̋y̋z̋ı̋ȷ̋" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\H{' + char + '}' - text += r'\H{\i}' # dotless i - text += r'\H{\j}' # dotless j + text += r"\H{" + char + "}" + text += r"\H{\i}" # dotless i + text += r"\H{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_ogonek(): r"""LaTex command for a ogonek on the character, e.g. \k{a}""" - answer = 'ĄB̨C̨D̨ĘF̨G̨H̨ĮJ̨K̨L̨M̨N̨ǪP̨Q̨R̨S̨T̨ŲV̨W̨X̨Y̨Z̨ąb̨c̨d̨ęf̨g̨h̨įj̨k̨l̨m̨n̨ǫp̨q̨r̨s̨t̨ųv̨w̨x̨y̨z̨ı̨ȷ̨' # noqa: E501 + answer = "ĄB̨C̨D̨ĘF̨G̨H̨ĮJ̨K̨L̨M̨N̨ǪP̨Q̨R̨S̨T̨ŲV̨W̨X̨Y̨Z̨ąb̨c̨d̨ęf̨g̨h̨įj̨k̨l̨m̨n̨ǫp̨q̨r̨s̨t̨ųv̨w̨x̨y̨z̨ı̨ȷ̨" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\k{' + char + '}' - text += r'\k{\i}' # dotless i - text += r'\k{\j}' # dotless j + text += r"\k{" + char + "}" + text += r"\k{\i}" # dotless i + text += r"\k{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_ring_above(): r"""LaTex command for a ring above the character, e.g. \r{a}""" - answer = 'ÅB̊C̊D̊E̊F̊G̊H̊I̊J̊K̊L̊M̊N̊O̊P̊Q̊R̊S̊T̊ŮV̊W̊X̊Y̊Z̊åb̊c̊d̊e̊f̊g̊h̊i̊j̊k̊l̊m̊n̊o̊p̊q̊r̊s̊t̊ův̊ẘx̊ẙz̊ı̊ȷ̊' # noqa: E501 + answer = "ÅB̊C̊D̊E̊F̊G̊H̊I̊J̊K̊L̊M̊N̊O̊P̊Q̊R̊S̊T̊ŮV̊W̊X̊Y̊Z̊åb̊c̊d̊e̊f̊g̊h̊i̊j̊k̊l̊m̊n̊o̊p̊q̊r̊s̊t̊ův̊ẘx̊ẙz̊ı̊ȷ̊" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\r{' + char + '}' - text += r'\r{\i}' # dotless i - text += r'\r{\j}' # dotless j + text += r"\r{" + char + "}" + text += r"\r{\i}" # dotless i + text += r"\r{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_double_inverted_breve(): r"""LaTex command for a double inverted breve on the character, e.g. \t{a}""" - answer = 'A͡AB͡BC͡CD͡DE͡EF͡FG͡GH͡HI͡IJ͡JK͡KL͡LM͡MN͡NO͡OP͡PQ͡QR͡RS͡ST͡TU͡UV͡VW͡WX͡XY͡YZ͡Za͡ab͡bc͡cd͡de͡ef͡fg͡gh͡hi͡ij͡jk͡kl͡lm͡mn͡no͡op͡pq͡qr͡rs͡st͡tu͡uv͡vw͡wx͡xy͡yz͡zı͡ıȷ͡ȷ' # noqa: E501 + answer = "A͡AB͡BC͡CD͡DE͡EF͡FG͡GH͡HI͡IJ͡JK͡KL͡LM͡MN͡NO͡OP͡PQ͡QR͡RS͡ST͡TU͡UV͡VW͡WX͡XY͡YZ͡Za͡ab͡bc͡cd͡de͡ef͡fg͡gh͡hi͡ij͡jk͡kl͡lm͡mn͡no͡op͡pq͡qr͡rs͡st͡tu͡uv͡vw͡wx͡xy͡yz͡zı͡ıȷ͡ȷ" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\t{' + char + '}' + char - text += r'\t{\i}\i' # dotless i - text += r'\t{\j}\j' # dotless j + text += r"\t{" + char + "}" + char + text += r"\t{\i}\i" # dotless i + text += r"\t{\j}\j" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_breve(): r"""LaTex command for a breve on the character, e.g. \u{a}""" - answer = 'ĂB̆C̆D̆ĔF̆ĞH̆ĬJ̆K̆L̆M̆N̆ŎP̆Q̆R̆S̆T̆ŬV̆W̆X̆Y̆Z̆ăb̆c̆d̆ĕf̆ğh̆ĭj̆k̆l̆m̆n̆ŏp̆q̆r̆s̆t̆ŭv̆w̆x̆y̆z̆ı̆ȷ̆' # noqa: E501 + answer = "ĂB̆C̆D̆ĔF̆ĞH̆ĬJ̆K̆L̆M̆N̆ŎP̆Q̆R̆S̆T̆ŬV̆W̆X̆Y̆Z̆ăb̆c̆d̆ĕf̆ğh̆ĭj̆k̆l̆m̆n̆ŏp̆q̆r̆s̆t̆ŭv̆w̆x̆y̆z̆ı̆ȷ̆" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\u{' + char + '}' - text += r'\u{\i}' # dotless i - text += r'\u{\j}' # dotless j + text += r"\u{" + char + "}" + text += r"\u{\i}" # dotless i + text += r"\u{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_double_vertical_line_above(): r"""LaTex command for a double vertical line above the character, e.g. \b{a}""" - answer = 'A̎B̎C̎D̎E̎F̎G̎H̎I̎J̎K̎L̎M̎N̎O̎P̎Q̎R̎S̎T̎U̎V̎W̎X̎Y̎Z̎a̎b̎c̎d̎e̎f̎g̎h̎i̎j̎k̎l̎m̎n̎o̎p̎q̎r̎s̎t̎u̎v̎w̎x̎y̎z̎ı̎ȷ̎' # noqa: E501 + answer = "A̎B̎C̎D̎E̎F̎G̎H̎I̎J̎K̎L̎M̎N̎O̎P̎Q̎R̎S̎T̎U̎V̎W̎X̎Y̎Z̎a̎b̎c̎d̎e̎f̎g̎h̎i̎j̎k̎l̎m̎n̎o̎p̎q̎r̎s̎t̎u̎v̎w̎x̎y̎z̎ı̎ȷ̎" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\U{' + char + '}' - text += r'\U{\i}' # dotless i - text += r'\U{\j}' # dotless j + text += r"\U{" + char + "}" + text += r"\U{\i}" # dotless i + text += r"\U{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_caron(): r"""LaTex command for a caron on the character, e.g. \v{a}""" - answer = 'ǍB̌ČĎĚF̌ǦȞǏJ̌ǨĽM̌ŇǑP̌Q̌ŘŠŤǓV̌W̌X̌Y̌Žǎb̌čďěf̌ǧȟǐǰǩľm̌ňǒp̌q̌řšťǔv̌w̌x̌y̌žı̌ȷ̌' # noqa: E501 + answer = "ǍB̌ČĎĚF̌ǦȞǏJ̌ǨĽM̌ŇǑP̌Q̌ŘŠŤǓV̌W̌X̌Y̌Žǎb̌čďěf̌ǧȟǐǰǩľm̌ňǒp̌q̌řšťǔv̌w̌x̌y̌žı̌ȷ̌" # noqa: E501 - text = '' + text = "" for char in list(alphabet): - text += r'\v{' + char + '}' - text += r'\v{\i}' # dotless i - text += r'\v{\j}' # dotless j + text += r"\v{" + char + "}" + text += r"\v{\i}" # dotless i + text += r"\v{\j}" # dotless j result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_symbols(): r"""LaTex command for symbols, e.g. \i""" - answer = 'ıȷłŁøØ' # noqa: E501 + answer = "ıȷłŁøØ" # noqa: E501 - text = '' - for char in list('ijlLoO'): - text += '\\' + char + text = "" + for char in list("ijlLoO"): + text += "\\" + char result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text def test_latex_dashes(): r"""LaTex command for dashes, e.g. \i""" - answer = '- – —' # noqa: E501 + answer = "- – —" # noqa: E501 - text = '- -- ---' + text = "- -- ---" result = decode_latex(text) check = encode_latex(result) - assert (result == answer and check == text) + assert result == answer and check == text diff --git a/reference_handler/tests/test_reference_handler.py b/reference_handler/tests/test_reference_handler.py index 094c69c..09477be 100644 --- a/reference_handler/tests/test_reference_handler.py +++ b/reference_handler/tests/test_reference_handler.py @@ -63,161 +63,157 @@ def test_reference_handler_imported(): def test_initialization(): - rf = _create_db('database.db') + rf = _create_db("database.db") assert rf.total_citations() == 0 - assert rf.total_citations(alias='my_alias') == 0 + assert rf.total_citations(alias="my_alias") == 0 assert rf.total_citations(reference_id=1) == 0 - assert rf.total_mentions(alias='my_alias') == 0 + assert rf.total_mentions(alias="my_alias") == 0 assert rf.total_mentions(reference_id=1) == 0 - assert rf.total_contexts(alias='my_alias') == 0 + assert rf.total_contexts(alias="my_alias") == 0 assert rf.total_contexts(reference_id=1) == 0 def test_add_new_cite_to_empty_db(): - rf = _create_db('database.db') + rf = _create_db("database.db") rf.cite( raw=lammps_citation, - alias='new_citation', - module='test_add_new_cite', + alias="new_citation", + module="test_add_new_cite", level=1, - note='This is a test' + note="This is a test", ) assert rf.total_citations() == 1 assert rf.total_citations(reference_id=1) == 1 - assert rf.total_citations(alias='new_citation') == 1 + assert rf.total_citations(alias="new_citation") == 1 assert rf.total_mentions(reference_id=1) == 1 - assert rf.total_mentions(alias='new_citation') == 1 + assert rf.total_mentions(alias="new_citation") == 1 assert rf.total_contexts(reference_id=1) == 1 - assert rf.total_contexts(alias='new_citation') == 1 + assert rf.total_contexts(alias="new_citation") == 1 def test_add_existing_citation(): - rf = _create_db('database.db') + rf = _create_db("database.db") rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='The main LAMMPS paper' + note="The main LAMMPS paper", ) rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='The main LAMMPS paper' + note="The main LAMMPS paper", ) assert rf.total_citations() == 1 assert rf.total_citations(reference_id=1) == 1 - assert rf.total_citations(alias='lammps_paper') == 1 + assert rf.total_citations(alias="lammps_paper") == 1 assert rf.total_mentions(reference_id=1) == 2 - assert rf.total_mentions(alias='lammps_paper') == 2 + assert rf.total_mentions(alias="lammps_paper") == 2 assert rf.total_contexts(reference_id=1) == 1 - assert rf.total_contexts(alias='lammps_paper') == 1 + assert rf.total_contexts(alias="lammps_paper") == 1 def test_add_new_context(): - rf = _create_db('database.db') + rf = _create_db("database.db") rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 1' + note="Context 1", ) rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 2' + note="Context 2", ) assert rf.total_citations() == 1 assert rf.total_citations(reference_id=1) == 1 - assert rf.total_citations(alias='lammps_paper') == 1 + assert rf.total_citations(alias="lammps_paper") == 1 assert rf.total_mentions(reference_id=1) == 2 - assert rf.total_mentions(alias='lammps_paper') == 2 + assert rf.total_mentions(alias="lammps_paper") == 2 assert rf.total_contexts(reference_id=1) == 2 - assert rf.total_contexts(alias='lammps_paper') == 2 + assert rf.total_contexts(alias="lammps_paper") == 2 rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=2, - note='Context 1' + note="Context 1", ) assert rf.total_citations() == 1 assert rf.total_citations(reference_id=1) == 1 - assert rf.total_citations(alias='lammps_paper') == 1 + assert rf.total_citations(alias="lammps_paper") == 1 assert rf.total_mentions(reference_id=1) == 3 - assert rf.total_mentions(alias='lammps_paper') == 3 + assert rf.total_mentions(alias="lammps_paper") == 3 assert rf.total_contexts(reference_id=1) == 3 - assert rf.total_contexts(alias='lammps_paper') == 3 + assert rf.total_contexts(alias="lammps_paper") == 3 rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS_2', + alias="lammps_paper", + module="LAMMPS_2", level=2, - note='Context 1' + note="Context 1", ) assert rf.total_citations() == 1 assert rf.total_citations(reference_id=1) == 1 - assert rf.total_citations(alias='lammps_paper') == 1 + assert rf.total_citations(alias="lammps_paper") == 1 assert rf.total_mentions(reference_id=1) == 4 - assert rf.total_mentions(alias='lammps_paper') == 4 + assert rf.total_mentions(alias="lammps_paper") == 4 assert rf.total_contexts(reference_id=1) == 4 - assert rf.total_contexts(alias='lammps_paper') == 4 + assert rf.total_contexts(alias="lammps_paper") == 4 def test_add_new_cite_to_existing_db(): - rf = _create_db('database.db') + rf = _create_db("database.db") rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 1' + note="Context 1", ) rf.cite( - raw=namd_citation, - alias='namd_paper', - module='NAMD', - level=1, - note='Context 1' + raw=namd_citation, alias="namd_paper", module="NAMD", level=1, note="Context 1" ) assert rf.total_citations() == 2 @@ -227,23 +223,23 @@ def test_add_new_cite_to_existing_db(): assert rf.total_mentions(reference_id=1) == 1 assert rf.total_mentions(reference_id=2) == 1 - assert rf.total_citations(alias='lammps_paper') == 1 - assert rf.total_citations(alias='namd_paper') == 1 + assert rf.total_citations(alias="lammps_paper") == 1 + assert rf.total_citations(alias="namd_paper") == 1 assert rf.total_mentions(alias="lammps_paper") == 1 - assert rf.total_mentions(alias='namd_paper') == 1 + assert rf.total_mentions(alias="namd_paper") == 1 assert rf.total_contexts(reference_id=1) == 1 assert rf.total_contexts(reference_id=2) == 1 - assert rf.total_contexts(alias='lammps_paper') == 1 - assert rf.total_contexts(alias='namd_paper') == 1 + assert rf.total_contexts(alias="lammps_paper") == 1 + assert rf.total_contexts(alias="namd_paper") == 1 def test_load_bibliography(): - rf = _create_db('database.db') + rf = _create_db("database.db") - bibfile = build_filenames.build_data_filename('library.bib') + bibfile = build_filenames.build_data_filename("library.bib") bib = rf.load_bibliography(bibfile=bibfile) @@ -257,62 +253,62 @@ def test_add_many_cites_and_many_contexts(): def _get_dump(outfile=None, level=None): - rf = _create_db('database.db') + rf = _create_db("database.db") - bibfile = build_filenames.build_data_filename('library.bib') + bibfile = build_filenames.build_data_filename("library.bib") bib = reference_handler.Reference_Handler.load_bibliography( - bibfile=bibfile, fmt='bibtex' + bibfile=bibfile, fmt="bibtex" ) rf.cite( - raw=bib['Jakobtorweihen.JCP.2006.125.224709'], - alias='Jakobtorweihen', - module='Code1', + raw=bib["Jakobtorweihen.JCP.2006.125.224709"], + alias="Jakobtorweihen", + module="Code1", level=1, - note='Context1' + note="Context1", ) rf.cite( - raw=bib['Afzal.JCED.2014.59.954'], - alias='Afzal', - module='Code2', + raw=bib["Afzal.JCED.2014.59.954"], + alias="Afzal", + module="Code2", level=1, - note='Context1' + note="Context1", ) rf.cite( - raw=bib['Kilaru.IECR.2008.47.910'], - alias='Kilaru', - module='Code3', + raw=bib["Kilaru.IECR.2008.47.910"], + alias="Kilaru", + module="Code3", level=1, - note='Context1' + note="Context1", ) rf.cite( - raw=bib['Argauer.USPatent.1972.3702886'], - alias='Argauer', - module='Code1', + raw=bib["Argauer.USPatent.1972.3702886"], + alias="Argauer", + module="Code1", level=3, - note='Context2' + note="Context2", ) rf.cite( - raw=bib['Afzal.JCED.2014.59.954'], - alias='Afzal', - module='Code4', + raw=bib["Afzal.JCED.2014.59.954"], + alias="Afzal", + module="Code4", level=1, - note='Context1' + note="Context1", ) rf.cite( - raw=bib['Jakobtorweihen.JCP.2006.125.224709'], - alias='Jakobtorweihen', - module='Code2', + raw=bib["Jakobtorweihen.JCP.2006.125.224709"], + alias="Jakobtorweihen", + module="Code2", level=3, - note='Context1' + note="Context1", ) rf.cite( - raw=bib['Afzal.JCED.2014.59.954'], - alias='Afzal', - module='Code2', + raw=bib["Afzal.JCED.2014.59.954"], + alias="Afzal", + module="Code2", level=1, - note='Context1' + note="Context1", ) dump = rf.dump(outfile=outfile, level=level) @@ -320,8 +316,7 @@ def _get_dump(outfile=None, level=None): @pytest.mark.parametrize( - 'name, count', - [('Afzal', 3), ('Jakobtorweihen', 2), ('Kilaru', 1), ('Argauer', 1)] + "name, count", [("Afzal", 3), ("Jakobtorweihen", 2), ("Kilaru", 1), ("Argauer", 1)] ) def test_dump(name, count): @@ -332,12 +327,11 @@ def test_dump(name, count): @pytest.mark.parametrize( - 'name, count', - [('Afzal', 3), ('Jakobtorweihen', 1), ('Kilaru', 1), ('Argauer', 0)] + "name, count", [("Afzal", 3), ("Jakobtorweihen", 1), ("Kilaru", 1), ("Argauer", 0)] ) def test_dump_with_level(name, count): - outfile = build_filenames.build_scratch_filename('outfile.bib') + outfile = build_filenames.build_scratch_filename("outfile.bib") dump = _get_dump(outfile=outfile, level=2) for item in dump: if name in item[1]: @@ -346,7 +340,7 @@ def test_dump_with_level(name, count): def test_dump_output(): - outfile = build_filenames.build_scratch_filename('outfile.bib') + outfile = build_filenames.build_scratch_filename("outfile.bib") dump = _get_dump(outfile=outfile) # noqa: F841 assert os.path.exists(outfile) is True @@ -354,35 +348,31 @@ def test_dump_output(): def test_cite_return(): - rf = _create_db('database.db') + rf = _create_db("database.db") lammps_id1 = rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 1' + note="Context 1", ) lammps_id2 = rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 1' + note="Context 1", ) lammps_id3 = rf.cite( raw=lammps_citation, - alias='lammps_paper', - module='LAMMPS', + alias="lammps_paper", + module="LAMMPS", level=1, - note='Context 2' + note="Context 2", ) namd_id = rf.cite( - raw=namd_citation, - alias='namd_paper', - module='NAMD', - level=1, - note='Context 1' + raw=namd_citation, alias="namd_paper", module="NAMD", level=1, note="Context 1" ) assert lammps_id1 == lammps_id2 diff --git a/reference_handler/utils.py b/reference_handler/utils.py index 9b32edc..12a3dd9 100644 --- a/reference_handler/utils.py +++ b/reference_handler/utils.py @@ -3,34 +3,33 @@ def _str_or_expr_to_bibtex(e): if isinstance(e, bibtexparser.bibdatabase.BibDataStringExpression): - return ' # '.join([_str_or_expr_to_bibtex(s) for s in e.expr]) + return " # ".join([_str_or_expr_to_bibtex(s) for s in e.expr]) elif isinstance(e, bibtexparser.bibdatabase.BibDataString): return e.name else: - return '{' + e + '}' + return "{" + e + "}" def entry_to_bibtex(entry): - bibtex = '' + bibtex = "" - bibtex += '@' + entry['ENTRYTYPE'] + '{' + entry['ID'] + bibtex += "@" + entry["ENTRYTYPE"] + "{" + entry["ID"] display_order = [i for i in sorted(entry)] - field_fmt = u",\n{indent}{field:<{field_max_w}} = {value}" + field_fmt = ",\n{indent}{field:<{field_max_w}} = {value}" - for field in [i for i in display_order if i not in ['ENTRYTYPE', 'ID']]: + for field in [i for i in display_order if i not in ["ENTRYTYPE", "ID"]]: try: bibtex += field_fmt.format( - indent=' ', + indent=" ", field=field, field_max_w=0, - value=_str_or_expr_to_bibtex(entry[field]) + value=_str_or_expr_to_bibtex(entry[field]), ) except TypeError: raise TypeError( - u"The field %s in entry %s must be a string" % - (field, entry['ID']) + "The field %s in entry %s must be a string" % (field, entry["ID"]) ) bibtex += "\n}\n\n" diff --git a/setup.cfg b/setup.cfg index c4caeeb..72b639c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,8 @@ omit = [flake8] exclude = docs +max-line-length = 88 +extend-ignore = E203 [versioneer] # Automatic version numbering scheme @@ -23,11 +25,3 @@ style = pep440 versionfile_source = reference_handler/_version.py versionfile_build = reference_handler/_version.py tag_prefix = '' - -[yapf] -based_on_style = google -column_limit = 79 -dedent_closing_brackets = True -each_dict_entry_on_separate_line = True -split_before_dot = True -blank_line_before_module_docstring = True From 465cf0b83ff14674e96f4a3259110a5381a4d35f Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 18:09:37 -0400 Subject: [PATCH 2/7] Modernize the CI and Release workflows Neither workflow has run in this repository's recorded history, and the last release was 0.9.1 in May 2021. Everything they depend on has moved on: actions/checkout@v1 -> v4 (v1 runs on a Node runtime GitHub actions/setup-python@v1 -> v5 has removed, so it fails outright) conda-incubator/setup-miniconda@v2 -> v3 codecov/codecov-action@v2 -> v4 pypa/gh-action-pypi-publish@master -> @release/v1, and repository_url is spelled repository-url now Python 3.7, 3.8 -> 3.11, 3.12 (3.7 and 3.8 are end of life and conda-forge no longer builds for them) The test environment had an empty `channels:`, so nothing came from conda-forge. It now asks for conda-forge, and for black, which the linters need. Lint with black rather than yapf, matching the Makefile and the rest of SEAMM. The deploy job ran `conda list` in a job that sets up plain Python and no conda, and installed the package before building it. Both dropped. Added workflow_dispatch to CI. GitHub disables a scheduled workflow after 60 days without a commit, and disabling it stops the push and pull_request triggers as well -- which is why the pull request for the bibtexparser pin got no CI at all. Being able to run it by hand makes that easier to notice and recover from. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/CI.yaml | 18 +++++++++----- .github/workflows/Release.yaml | 39 +++++++++++++------------------ devtools/conda-envs/test_env.yaml | 7 +++--- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 02e3b33..94a6481 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -13,14 +13,20 @@ on: # Run by default Sunday morning at 3:30: # Scheduled workflows run on the latest commit on the default or base branch. # (from https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule) + # + # GitHub disables a scheduled workflow after 60 days without a commit to the + # repository, and disabling it stops the push and pull_request triggers too. + # If the badge goes stale, re-enable it on the Actions tab; workflow_dispatch + # below gives a way to run it by hand. - cron: "30 3 * * 0" + workflow_dispatch: jobs: lint: name: Lint ubuntu-latest Py3.8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Additional info about the build shell: bash run: | @@ -28,7 +34,7 @@ jobs: df -h ulimit -a # More info on options: https://github.com/conda-incubator/setup-miniconda - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: python-version: 3.8 environment-file: devtools/conda-envs/test_env.yaml @@ -55,16 +61,16 @@ jobs: strategy: matrix: os: [macOS-latest, ubuntu-latest] - python-version: [3.7, 3.8] + python-version: ["3.11", "3.12"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Additional info about the build shell: bash run: | uname -a df -h ulimit -a - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python-version }} environment-file: devtools/conda-envs/test_env.yaml @@ -82,7 +88,7 @@ jobs: run: | pytest -v --cov=reference_handler --cov-report=xml --color=yes reference_handler/tests/ - name: CodeCov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unittests diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml index 36639a6..0bfa228 100644 --- a/.github/workflows/Release.yaml +++ b/.github/workflows/Release.yaml @@ -17,10 +17,10 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.8] + python-version: ["3.12"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Additional info about the build shell: bash @@ -30,7 +30,7 @@ jobs: ulimit -a # More info on options: https://github.com/conda-incubator/setup-miniconda - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python-version }} environment-file: devtools/conda-envs/test_env.yaml @@ -56,8 +56,9 @@ jobs: shell: bash -l {0} run: | - flake8 reference_handler reference_handler/tests - yapf --diff --recursive reference_handler + black --extend-exclude '_version.py' --check --diff \ + reference_handler reference_handler/tests + flake8 --color never reference_handler reference_handler/tests test: name: Test ${{ matrix.os }} Py${{ matrix.python-version }} @@ -66,10 +67,10 @@ jobs: strategy: matrix: os: [macOS-latest, ubuntu-latest] - python-version: [3.7, 3.8] + python-version: ["3.11", "3.12"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Additional info about the build shell: bash @@ -80,7 +81,7 @@ jobs: # More info on options: https://github.com/conda-incubator/setup-miniconda - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python-version }} environment-file: devtools/conda-envs/test_env.yaml @@ -107,7 +108,7 @@ jobs: pytest -v --cov=reference_handler --cov-report=xml --color=yes reference_handler/tests/ - name: CodeCov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unittests @@ -122,24 +123,16 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.8] + python-version: ["3.12"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install package - - # conda setup requires this special shell - shell: bash -l {0} - run: | - python -m pip install . --no-deps - conda list - - name: Install pypa/build run: | python -m pip install build --user @@ -150,13 +143,13 @@ jobs: ls -l dist/ - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.test_pypi_password }} - repository_url: https://test.pypi.org/legacy/ + repository-url: https://test.pypi.org/legacy/ skip_existing: true - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.pypi_password }} diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 81a1b4f..2b2de0e 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -1,11 +1,15 @@ name: test + channels: + - conda-forge + dependencies: # Base depends - python - pip # Testing + - black - codecov - coverage - flake8 @@ -16,7 +20,4 @@ dependencies: # Pip-only installs - pip: - bibtexparser<2 - - # Testing - - yapf From 71fb2e3b0195511a7970207850e116ad39fcc3cf Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 18:11:25 -0400 Subject: [PATCH 3/7] Trigger CI after re-enabling the workflow From 527438baf9c919bca373cfa643c84d7b5e2431ae Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 18:15:53 -0400 Subject: [PATCH 4/7] Actually switch CI's linter to black The previous commit changed the lint step in Release.yaml but not in CI.yaml: the two spell the flake8 invocation differently, and the edit matched only one of them, so CI went on calling yapf -- which the same commit had removed from the test environment. Hence "yapf: command not found". CI.yaml's lint job also has no matrix, so its name and Python version are written out rather than taken from one. Both were still on 3.8. Verified by running exactly what CI runs: black, flake8 and the tests are clean. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/CI.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 94a6481..0fd888e 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -23,7 +23,7 @@ on: jobs: lint: - name: Lint ubuntu-latest Py3.8 + name: Lint ubuntu-latest Py3.12 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -36,7 +36,7 @@ jobs: # More info on options: https://github.com/conda-incubator/setup-miniconda - uses: conda-incubator/setup-miniconda@v3 with: - python-version: 3.8 + python-version: "3.12" environment-file: devtools/conda-envs/test_env.yaml activate-environment: test auto-update-conda: false @@ -51,8 +51,9 @@ jobs: - name: Run linters shell: bash -l {0} run: | - flake8 reference_handler - yapf --diff --recursive reference_handler + black --extend-exclude '_version.py' --check --diff \ + reference_handler reference_handler/tests + flake8 --color never reference_handler reference_handler/tests test: name: Test ${{ matrix.os }} Py${{ matrix.python-version }} From 23d83b93fee37638a4be8916c8418700f39d1028 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 18:21:26 -0400 Subject: [PATCH 5/7] Make versioneer work on modern Python The bundled versioneer 0.18 called configparser.SafeConfigParser() and ConfigParser.readfp(), both removed in Python 3.12, so the package could not be built there at all: AttributeError: module 'configparser' has no attribute 'SafeConfigParser' That is why the workflows were pinned to Python 3.7 and 3.8, and it would also stop anyone pip-installing a new release on a current Python. The SEAMM packages carry the same versioneer 0.18 with exactly these two fixes already applied; versioneer.py here is now byte-identical to the copy they build with. `pip install .` succeeds on 3.12 and reports the version from git as before. Co-Authored-By: Claude Opus 5 (1M context) --- versioneer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versioneer.py b/versioneer.py index 64fea1c..3aa5da3 100644 --- a/versioneer.py +++ b/versioneer.py @@ -339,9 +339,9 @@ def get_config_from_root(root): # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() + parser = configparser.ConfigParser() with open(setup_cfg, "r") as f: - parser.readfp(f) + parser.read_file(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): From e5eca974e30d027f64397fede39cec630a193d1b Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 19:19:06 -0400 Subject: [PATCH 6/7] Publish to PyPI with trusted publishing There is no pypi_password secret on this repository or on the MolSSI organization, so the publish steps could never have worked. Rather than add an API token, use PyPI's trusted publishing: the job asks GitHub for a short-lived OIDC token and PyPI checks it against the publisher registered for the project. Nothing to store, nothing to rotate, and it cannot be used from anywhere else. The deploy job now requests `id-token: write` and the two publish steps no longer take a password. Also corrected skip_existing to skip-existing, which is how the action spells it now. This needs a one-time registration on PyPI (and, for the Test PyPI step, on test.pypi.org) naming owner MolSSI, repository reference_handler, workflow Release.yaml, and no environment. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/Release.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml index 0bfa228..7d1b20a 100644 --- a/.github/workflows/Release.yaml +++ b/.github/workflows/Release.yaml @@ -120,6 +120,12 @@ jobs: if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') runs-on: ${{ matrix.os }} needs: [lint, test] + + # Trusted publishing: PyPI checks this token against the publisher registered + # for the project, so there is no API token to store or rotate. + permissions: + id-token: write + contents: read strategy: matrix: os: [ubuntu-latest] @@ -145,11 +151,8 @@ jobs: - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.test_pypi_password }} repository-url: https://test.pypi.org/legacy/ - skip_existing: true + skip-existing: true - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.pypi_password }} From f6efd318b9ddca0de79731c8fece4000aaf536b2 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Sun, 20 Sep 2026 19:26:22 -0400 Subject: [PATCH 7/7] Drop the Test PyPI step Trusted publishing to test.pypi.org needs its own registration on that site, a separate one from PyPI's. Without it the step fails, and since it ran before the real publish it would block the release. For a package that releases rarely it is not worth the second registration. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/Release.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/Release.yaml b/.github/workflows/Release.yaml index 7d1b20a..a99f15e 100644 --- a/.github/workflows/Release.yaml +++ b/.github/workflows/Release.yaml @@ -148,11 +148,5 @@ jobs: python -m build --sdist --wheel --outdir dist/ ls -l dist/ - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - skip-existing: true - - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1