Skip to content

Commit d596189

Browse files
Merge pull request #92 from dvklopfenstein/dev
Fix dict key potential error; replaced removing files/dirs using os.system for greater safety
2 parents 072eea2 + be4503e commit d596189

11 files changed

Lines changed: 136 additions & 109 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
## Summary
88

99
* [**Unreleased**](#unreleased)
10+
* [**Release 2026-04-14 v0.3.2**]dir key err fixed; Replaced removing files/dirs using system
1011
* [**Release 2025-12-15 v0.3.1**]Removed links to cite which went roguer (Gambling) than it was before (creepy use of your data).
1112
* [**Release 2025-12-01 v0.3.0**](#release-2025-12-01-v030) Updated for new NIH iCite format which may no longer supply keys such as `author`
1213
* [**Release 2025-09-06 v0.2.0**](#release-2025-09-06-v020) Updated for new NIH iCite format which may no longer have fields: cited_by_clin cited_by references provisional

pmidcite/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
__copyright__ = 'Copyright (C) 2019-present, DV Klopfenstein, PhD. All rights reserved'
44
__author__ = 'DV Klopfenstein, PhD'
5-
__version__ = '0.3.1'
5+
__version__ = '0.3.2'
66

77
# Copyright (C) 2019-present, DV Klopfenstein, PhD. All rights reserved

pmidcite/eutils/cmds/pubmed.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
__copyright__ = "Copyright (C) 2016-present DV Klopfenstein, PhD. All rights reserved."
55
__license__ = "AGPL-3.0"
66

7-
from os import system
8-
from os import getcwd
9-
from os.path import exists
107
import sys
8+
import os
9+
import os.path as op
10+
from pathlib import Path
1111
from pmidcite.eutils.cmds.base import EntrezUtilities
1212
from pmidcite.eutils.cmds.esearch import ESearch
1313

@@ -31,8 +31,7 @@ def dnld_query_pmids(self, query, num_ids_p_epost=10):
3131

3232
def _dnld_wr_all(self, fout_pubmed, efetch_idxs, efetch_params):
3333
"""Download and write all PMIDs PubMed text entries into one file"""
34-
if exists(fout_pubmed):
35-
system(f'rm {fout_pubmed}')
34+
Path(fout_pubmed).unlink(missing_ok=True)
3635
if not efetch_idxs:
3736
return
3837
for desc, start, pmids_exp, querykey in efetch_idxs:
@@ -50,7 +49,7 @@ def _dnld_query(self, query):
5049

5150
def dnld_wr1_per_pmid(self, pmids, force_download, dir_pubmed_txt, pmid2name=None):
5251
"""Download and write one PubMed text file entry per PMID"""
53-
if not exists(dir_pubmed_txt):
52+
if not op.exists(dir_pubmed_txt):
5453
dir_pubmed_txt = self._err_exists(dir_pubmed_txt)
5554
pmid_nt_list = self.get_pmid_nt_list(pmids, force_download, dir_pubmed_txt, pmid2name)
5655
efetch_idxs, efetch_params = self.epost_ids(pmids, 'pubmed', 10, 1, **self.medline_text)
@@ -59,8 +58,8 @@ def dnld_wr1_per_pmid(self, pmids, force_download, dir_pubmed_txt, pmid2name=Non
5958
@staticmethod
6059
def _err_exists(dir_pubmed_txt):
6160
"""Print warning message if dir not exist, return current working directory"""
62-
cwd = getcwd()
63-
print(f'**WARNING: DIR({dir_pubmed_txt}) NOT EXIST RETURNING CWD({getcwd()})')
61+
cwd = os.getcwd()
62+
print(f'**WARNING: DIR({dir_pubmed_txt}) NOT EXIST RETURNING CWD({cwd})')
6463
return cwd
6564

6665
def dnld_texts(self, efetch_idxs, efetch_params):

pmidcite/icite/entry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def get_aart_translation(self):
268268
lst.append('H' if dct['human'] != 0.0 else '.')
269269
lst.append('A' if dct['animal'] != 0.0 else '.')
270270
lst.append('M' if dct['molecular_cellular'] != 0.0 else '.')
271-
lst.append('C' if dct['is_clinical'] else '.')
271+
lst.append('C' if dct.get('is_clinical') else '.')
272272
lst.append('c' if dct.get('cited_by_clin') else '.')
273273
return ''.join(lst)
274274

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
66
[project]
77
name = "pmidcite"
88
description="Turbocharge a PubMed literature search using citation data from the NIH"
9-
version = "0.3.1"
9+
version = "0.3.2"
1010
license = "AGPL-3.0-or-later"
1111
authors = [
1212
{name = 'DV Klopfenstein, PhD', email = 'dvklopfenstein@protonmail.com'},

tests/icite.py

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,40 @@
44
__copyright__ = "Copyright (C) 2019-present, DV Klopfenstein, PhD. All rights reserved."
55
__author__ = "DV Klopfenstein, PhD"
66

7+
import os
8+
import os.path as op
9+
import shutil
710
from os import system
8-
from os import mkdir
9-
from os import environ
10-
from os.path import join
11-
from os.path import dirname
12-
from os.path import abspath
13-
from os.path import exists
14-
from os.path import getmtime
11+
from pathlib import Path
1512
from glob import glob
1613
from sys import stdout
1714
from pmidcite.icite.pmid_dnlder import NIHiCiteDownloader
1815

19-
DIR_TEST = dirname(abspath(__file__))
20-
DIR_TESTDATA = join(DIR_TEST, "data")
21-
DIR_ICITE = abspath(join(DIR_TEST, "./icite"))
22-
DIR_REPO = abspath(join(DIR_TEST, ".."))
23-
DL = environ.get('DLCYG')
16+
DIR_TEST = op.dirname(op.abspath(__file__))
17+
DIR_TESTDATA = op.join(DIR_TEST, "data")
18+
DIR_ICITE = op.abspath(op.join(DIR_TEST, "./icite"))
19+
DIR_REPO = op.abspath(op.join(DIR_TEST, ".."))
20+
DL = os.environ.get('DLCYG')
2421

2522

2623
def get_dnld_files(glob_pattern):
2724
"""Get the filenames of downloaded files matching the researcher's glob pattern"""
28-
return glob(join(DL, glob_pattern))
25+
return glob(op.join(DL, glob_pattern))
2926

3027
def get_filename_test(basename):
3128
"""Get the full filename of a test file, basename"""
32-
return join(DIR_TEST, basename)
29+
return op.join(DIR_TEST, basename)
3330

3431
def get_filename_testdata(basename):
3532
"""Get the full filename of a test file, basename"""
36-
return join(DIR_TESTDATA, basename)
33+
return op.join(DIR_TESTDATA, basename)
3734

3835
def dir_icite_clobber(prt=stdout):
3936
"""Create an empty dir, ./src/tests/icite, removing old contents if necessary"""
40-
cmd = 'rm -rf {ICITE}; mkdir {ICITE}'.format(ICITE=DIR_ICITE)
41-
system(cmd)
37+
shutil.rmtree(DIR_ICITE, ignore_errors=True)
38+
Path(DIR_ICITE).mkdir(parents=True, exist_ok=True)
4239
if prt:
43-
prt.write(f"{cmd}\n")
40+
print('rm -rf', DIR_ICITE)
4441

4542
def dir_icite_wc_l(prt=stdout):
4643
"""Print count of p{PMID}.py files in dir ./src/tests/icite"""
@@ -53,10 +50,10 @@ def dir_icite_wc_l(prt=stdout):
5350
def mk_dir(dir_name, rmdir=False):
5451
"""Get the directory where data downloaded from NIH-OCC are stored"""
5552
if rmdir:
56-
system(f'rm -rf {dir_name}')
57-
if not exists(dir_name):
58-
mkdir(dir_name)
59-
print(f'**CREATED DIR: {abspath(dir_name)}')
53+
shutil.rmtree(dir_name, ignore_errors=True)
54+
if not op.exists(dir_name):
55+
os.mkdir(dir_name)
56+
print(f'**CREATED DIR: {op.abspath(dir_name)}')
6057
return dir_name
6158

6259

@@ -65,12 +62,12 @@ class ICiteTester:
6562

6663
def __init__(self):
6764
self.dir_icite = mk_dir(DIR_ICITE)
68-
self.icite_files = join(self.dir_icite, '*.py')
65+
self.icite_files = op.join(self.dir_icite, '*.py')
6966

7067
def rm_icitefiles(self):
7168
"""Remove downloaded NIH-OCC iCite files"""
72-
if list(glob(self.icite_files)):
73-
system(f'rm {self.icite_files}')
69+
for fname in list(glob(self.icite_files)):
70+
os.remove(fname)
7471
assert not list(glob(self.icite_files)), 'BAD INITIAL CLEAN UP'
7572

7673
def get_paper(self, pmid, force_download=False, do_prt=True):
@@ -86,7 +83,7 @@ def get_paper(self, pmid, force_download=False, do_prt=True):
8683

8784
def get_f2mtime(self, min_files):
8885
"""Get mofification times of globbed files"""
89-
f2mtime = {fin:getmtime(fin) for fin in glob(self.icite_files)}
86+
f2mtime = {fin:op.getmtime(fin) for fin in glob(self.icite_files)}
9087
assert len(f2mtime) >= min_files, \
9188
f'iCite FILES NOT DOWNLOADED {len(f2mtime)=} < min_files({min_files})'
9289
return f2mtime

tests/test_cfg_icite.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
#!/usr/bin/env python3
22
"""Test cfg file documentation matches cfg init"""
33

4-
from os import system
54
from os import stat
6-
from os.path import join
7-
from os.path import exists
8-
from os.path import basename
9-
from os.path import relpath
5+
import os.path as op
6+
from pathlib import Path
107
from sys import argv
118
import filecmp
129
from pmidcite.cfg import Cfg
1310
from pmidcite.cfgini import wr_rcfile
1411
from tests.icite import DIR_REPO
12+
from tests.utils import cat
1513

1614

1715
# pylint: disable=line-too-long
1816
def test_cfg_desecriptive():
1917
"""Test default cfg file that contains detailed comments"""
20-
file_cfg = join(DIR_REPO, 'test_icite.cfg')
18+
file_cfg = op.join(DIR_REPO, 'test_icite.cfg')
2119

2220
# Remove test configuration file and test that it does not exist
23-
system(f'rm -f {file_cfg}')
24-
assert not exists(file_cfg)
21+
Path(file_cfg).unlink(missing_ok=True)
22+
assert not op.exists(file_cfg)
2523

2624
# Test that non-existing configuration file can not be read by Cfg
2725
obj = Cfg(check=False, prt_fullname=False)
2826
obj.set_cfg(file_cfg)
2927
assert not obj.rd_rc()
3028

3129
# Write configuration file. Test that an exiting cfg file is not overwritten
32-
system(f'cat {file_cfg}')
30+
cat(file_cfg)
3331
assert wr_rcfile(file_cfg, force=False)
34-
system(f'cat {file_cfg}')
35-
assert exists(file_cfg)
32+
cat(file_cfg)
33+
assert op.exists(file_cfg)
3634
assert not wr_rcfile(file_cfg, force=False)
3735

3836
# Read configuration file, file_cfg
@@ -42,46 +40,46 @@ def test_cfg_desecriptive():
4240
f'UNEXPECTED FILENAME: EXP({file_cfg}) ACT({next(iter(cfg))})'
4341

4442
# Test that the values for the new cfg file are the default values
45-
fin_base = basename(file_cfg)
43+
fin_base = op.basename(file_cfg)
4644
for key, actual in obj.cfgparser['pmidcite'].items():
4745
print(f'{fin_base}: {key} {actual}')
4846
expected = Cfg.dfltdct['pmidcite'][key]
4947
assert actual == expected, \
5048
f'{file_cfg}: KEY({key}) ACTUAL({actual}) != EXPECTED({expected})'
5149
print(stat(file_cfg))
52-
system(f'rm {file_cfg}')
50+
Path(file_cfg).unlink(missing_ok=True)
5351
print('PASSED: cfg init with comments')
5452

5553

5654
def test_cfg_example(update_example=False):
5755
"""Test that the config file example is up-to-date"""
58-
fin_cfg = join(DIR_REPO, 'test_icite.cfg')
59-
fin_ex = join(DIR_REPO, 'doc/example_cfg/.pmidciterc')
56+
fin_cfg = op.join(DIR_REPO, 'test_icite.cfg')
57+
fin_ex = op.join(DIR_REPO, 'doc/example_cfg/.pmidciterc')
6058
wr_rcfile(fin_cfg, force=True)
6159
wr_rcfile(fin_ex, force=update_example)
6260
# Compare the contents of the two files
6361
assert filecmp.cmp(fin_cfg, fin_ex, shallow=False), \
64-
f'EXP({relpath(fin_ex)}) != ACT({relpath(fin_cfg)})'
65-
system('rm {fin_cfg}')
62+
f'EXP({op.relpath(fin_ex)}) != ACT({op.relpath(fin_cfg)})'
63+
Path(fin_cfg).unlink(missing_ok=True)
6664
print('PASSED: Cfg example matches cfg default')
6765

6866

6967
def test_cfg_icite():
7068
"""Test cfg file documentation matches cfg init"""
7169
# NIH iCite configuration file
72-
file_cfg = join(DIR_REPO, 'test_icite.cfg')
70+
file_cfg = op.join(DIR_REPO, 'test_icite.cfg')
7371

7472
# Remove test configuration file and test that it can no longer be read by Cfg
75-
system(f'rm -f {file_cfg}')
73+
Path(file_cfg).unlink(missing_ok=True)
7674
obj = Cfg(check=False, prt_fullname=False)
7775
obj.set_cfg(file_cfg)
7876
assert not obj.rd_rc()
7977

8078
# Write configuration file
81-
system(f'cat {file_cfg}')
79+
cat(file_cfg)
8280
assert obj.wr_rc()
83-
system('cat {file_cfg}')
84-
assert exists(file_cfg)
81+
cat(file_cfg)
82+
assert op.exists(file_cfg)
8583
assert not obj.wr_rc()
8684

8785
# Read configuration file
@@ -95,21 +93,21 @@ def test_cfg_icite():
9593
f"EXP({Cfg.dfltdct['pmidcite']['dir_icite_py']}) "
9694
f"ACT({obj.cfgparser['pmidcite']['dir_icite_py']})")
9795
assert obj.cfgparser['pmidcite']['dir_pubmed_txt'] == Cfg.dfltdct['pmidcite']['dir_pubmed_txt']
98-
system(f'rm {file_cfg}')
96+
Path(file_cfg).unlink(missing_ok=True)
9997
print('PASSED: cfg init with no comments')
10098

10199

102100
def test_cfg_eutils():
103101
"""Test writing and reading the configuration file"""
104102
# NIH iCite configuration file
105-
file_cfg = join(DIR_REPO, 'test_eutils.cfg')
103+
file_cfg = op.join(DIR_REPO, 'test_eutils.cfg')
106104
obj = Cfg(check=False)
107105
obj.set_cfg(file_cfg)
108106

109107
# Write configuration file
110-
system(f'rm -f {file_cfg}')
108+
Path(file_cfg).unlink(missing_ok=True)
111109
assert obj.wr_rc()
112-
assert exists(file_cfg)
110+
assert op.exists(file_cfg)
113111
assert not obj.wr_rc()
114112

115113
# Newly created cfg file should have default values for private data
@@ -119,7 +117,7 @@ def test_cfg_eutils():
119117
assert obj.cfgparser['pmidcite']['apikey'] == Cfg.dfltdct['pmidcite']['apikey']
120118
assert obj.cfgparser['pmidcite']['tool'] == Cfg.dfltdct['pmidcite']['tool']
121119

122-
system(f'rm {file_cfg}')
120+
Path(file_cfg).unlink(missing_ok=True)
123121
print('PASSED: cfg init private values are default')
124122

125123

tests/test_dnld_cites_refs.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22
"""Test downloading a paper, paper + refs, paper + cites, paper + all"""
33

4-
from os import system
4+
import os
55
from sys import stdout
66
from glob import glob
77
from pmidcite.icite.pmid_dnlder import NIHiCiteDownloader
@@ -16,6 +16,7 @@ def test_dnld_cites_refs():
1616
"""Test downloading a paper, paper + refs, paper + cites, paper + all"""
1717

1818
pmid = 20640201
19+
os.makedirs(DIR_ICITE, exist_ok=True)
1920
dnldr_top = NIHiCiteDownloader(DIR_ICITE, FORCE_DNLD)
2021
_run("TEST(plain)", dnldr_top, pmid)
2122

@@ -45,11 +46,12 @@ def _compare(test_name, pmid, entry_lst, desc):
4546

4647
def _run(test_name, dnldr, pmid):
4748
"""Download PMIDs"""
48-
system(f'rm -f {dnldr.dir_dnld}/p*.py')
49+
globstr = f'{dnldr.dir_dnld}/p*.py'
50+
for fname in glob(globstr):
51+
os.remove(fname)
4952
pmid2icitepaper = dnldr.get_pmid2paper({pmid}, pmid2note=None)
5053

5154
# Get filenames (p{PMID}.py) downloaded from NIH's citation database
52-
globstr = f'{dnldr.dir_dnld}/p*.py'
5355
pmids_dnlded = glob(globstr)
5456
print(f'{len(pmids_dnlded)} NIH icites downloaded')
5557
## print('{N} pmid2icitepaper for PMID({P}); {O} assc PMIDs'.format(

0 commit comments

Comments
 (0)