Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SciELO Processing

Conjunto de utilitários para a produção de tabulações com metadados e métricas
e também para o envio de dados à parceiros.


## Requisitos:
* Python 2.7


## Exportação de dados ao DOAJ

Esta integração é realizada por meio de um robô que obtém os metadados dos
documentos a partir do ArticleMeta, já codificados em XML conforme o _schema_
do DOAJ, e os envia, um a um, através do formulário de submissão de artigos em
XML, do site do DOAJ. Este processo não é ótimo mas é o único que nos permite
enviar metadados em múltiplos idiomas.

Ao depositar um documento, o DOAJ verifica automaticamente se os _ISSNs_
informados correspondem exatamente com os cadastrados em sua base de dados e,
no caso de qualquer divergência, o depósito é rejeitado. A fim de contornar
este problema, é possível construir uma _base de correções_ que poderá ser
utilizada pelo utilitário de depósito. A _base de correções_ é produzida a
partir de consultas às bases do DOAJ e, quando aplicada, modifica os metadados
dos documentos de forma que os _ISSNs_ fiquem conforme esperado pelo DOAJ.

Para produzir a _base de correções_ e armazená-la em `/tmp/corr.jsonl`:

```bash
python export/gen_doaj_correctionsdb.py gen-correctionsdb > /tmp/corr.jsonl
```

Para executar o depósito dos documentos no DOAJ:

```bash
PROCESSING_SETTINGS_FILE=myconfig.ini processing_export_doaj --corrections_db /tmp/corr.jsonl --user US3R --password P4SSW0RD
```

Note que: (a) será necessário criar um arquivo de configurações e atribuí-lo
à variável `PROCESSING_SETTINGS_FILE`. Este arquivo poderá ser baseado no
[exemplo](https://raw.githubusercontent.com/scieloorg/processing/master/config.ini-TEMPLATE)
fornecido no projeto e (b) que o utilitário `processing_export_doaj` aceita
opções que podem ser usadas de forma a delimitar o conjunto de dados que serão
depositados no DOAJ. Para mais detalhes execute
`PROCESSING_SETTINGS_FILE=myconfig.ini processing_export_doaj --help`.
13 changes: 0 additions & 13 deletions README.rst

This file was deleted.

121 changes: 115 additions & 6 deletions export/exdoaj.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def _config_logging(logging_level='INFO', logging_file=None):
class Dumper(object):

def __init__(self, collection, issns=None, output_file=None, from_date=FROM,
user=None, password=None, api_token=None):
user=None, password=None, api_token=None, corrections_db=None, validate_schema=False):

self._articlemeta = utils.articlemeta_server()
self.collection = collection
Expand All @@ -63,8 +63,10 @@ def __init__(self, collection, issns=None, output_file=None, from_date=FROM,
self.password = password
self.issns = issns or [None]
self.session = self.authenticated_session()
self.parse_schema()
self.validate_schema = validate_schema
self.doaj_schema = self.parse_schema() if self.validate_schema else None
self.doaj_articles = Articles(usertoken=api_token)
self.corrections_db = corrections_db

def _doaj_id_by_meta(self, issn, publication_year, title):
### Query by metadata
Expand Down Expand Up @@ -133,7 +135,7 @@ def parse_schema(self):
logger.error('Fail to parse XML')
return False

self.doaj_schema = sch
return sch

def authenticated_session(self):
auth_url = 'https://doaj.org/account/login'
Expand All @@ -159,7 +161,6 @@ def authenticated_session(self):
return session

def xml_is_valid(self, xml):

try:
xml = StringIO(xml)
xml_doc = etree.parse(xml)
Expand Down Expand Up @@ -233,15 +234,103 @@ def run(self):
logger.error('Fail to read document: %s_%s' % (document.publisher_id, document.collection_acronym))
xml = u''

if not self.xml_is_valid(xml):
if self.validate_schema and not self.xml_is_valid(xml):
logger.error('Fail to parse xml document: %s_%s' % (document.publisher_id, document.collection_acronym))
continue

logger.info('Sending document: %s_%s' % (document.publisher_id, document.collection_acronym))
filename = '%s_%s.xml' % (document.publisher_id, document.collection_acronym)

# aplica a correção aos ISSNs.
if self.corrections_db is not None:
xml = self._fix_issns(xml)

self.send_xml(filename, xml)

def _get_doaj_issns(self, issn):
corrected = self.corrections_db.find(issn)
result = {}
for issn in corrected.get("issns", []):
result[issn["type"]] = issn["id"]

return result

def _fix_issns(self, xml_data):
et = etree.parse(BytesIO(xml_data.encode("utf-8")))
issns = [i.text for i in et.xpath("/records/record/issn | /records/record/eissn")]
for issn in issns:
try:
issns_from_doaj = self._get_doaj_issns(issn)
except ValueError:
logger.info('could not find corrected issns for "%s"', issn)
continue
else:
result = replace_issns(et, pissn=issns_from_doaj.get("pissn"), eissn=issns_from_doaj.get("eissn"))
logger.debug('the xml "%s" was replaced by "%s"', xml_data, result)
return result
else:
return xml_data


def replace_issns(et, pissn=None, eissn=None):
assert any([pissn, eissn])

record_node = et.find("record")
journalTitle_node = et.xpath("/records/record/journalTitle")[0]

for node in et.xpath("/records/record/issn | /records/record/eissn"):
del(record_node[record_node.index(node)])

if eissn:
new_eissn_node = etree.Element("eissn")
new_eissn_node.text = eissn
record_node.insert(
record_node.index(journalTitle_node) + 1,
new_eissn_node
)

if pissn:
new_issn_node = etree.Element("issn")
new_issn_node.text = pissn
record_node.insert(
record_node.index(journalTitle_node) + 1,
new_issn_node
)

return etree.tostring(et, encoding="unicode", pretty_print=False)


class CorrectionsDB(object):
def __init__(self, data):
self._data = tuple(data)
def _make_issn_getter(issn_type):
def _issn_getter(item):
for issn_data in item.get('issns', []):
if issn_data['type'] == issn_type:
return issn_data['id']

return _issn_getter

self._index = self._create_index(self._data, _make_issn_getter('eissn'))
self._index.update(self._create_index(self._data, _make_issn_getter('pissn')))

def _create_index(self, data, func):
"""Este índice mapeia cada ISSN a uma posição na lista `data`, de forma
que não seja necessário iterar sobre a lista em busca do item desejado.
"""
result = {func(item): i for i, item in enumerate(data)}
try:
del(result[None])
except KeyError:
pass
return result

def find(self, issn):
pos = self._index.get(issn)
if pos is None:
raise ValueError
return self._data[pos]


def main():

Expand Down Expand Up @@ -303,6 +392,18 @@ def main():
help='Logggin level'
)

parser.add_argument(
'--validate_schema',
action='store_true',
help='Validate each document against the DOAJ Schema before submitting',
)

parser.add_argument(
'--corrections_db',
help='Path to the corrections database file',
type=argparse.FileType("r"),
)

args = parser.parse_args()
_config_logging(args.logging_level, args.logging_file)
logger.info('Dumping data for: %s' % args.collection)
Expand All @@ -321,8 +422,16 @@ def main():
else:
issns = issns_from_file if issns_from_file else []

if args.corrections_db:
corrections_data = [json.loads(line) for line in args.corrections_db.readlines()]
corrections_db = CorrectionsDB(corrections_data)
logger.info('a database of corrections will be used to fix ISSNs before sending the data')
else:
corrections_db = None
logger.info('no database of corrections was given, the processing will proceed anyway')

dumper = Dumper(
args.collection, issns, from_date=args.from_date, user=args.user,
password=args.password)
password=args.password, corrections_db=corrections_db)

dumper.run()
Loading