forked from MIRRI/mirri_utils
First import
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
from mirri.biolomics.remote.biolomics_client import BiolomicsMirriClient
|
||||
from mirri.biolomics.remote.endoint_names import GROWTH_MEDIUM_WS
|
||||
from mirri.entities.growth_medium import GrowthMedium
|
||||
from mirri.biolomics.serializers.growth_media import get_growth_medium_record_name
|
||||
|
||||
|
||||
def get_or_create_or_update_growth_medium(client: BiolomicsMirriClient,
|
||||
growth_medium: GrowthMedium,
|
||||
update=False):
|
||||
response = get_or_create_growth_medium(client, growth_medium)
|
||||
|
||||
new_gm = response['record']
|
||||
created = response['created']
|
||||
if created:
|
||||
return {'record': new_gm, 'created': created, 'updated': False}
|
||||
|
||||
if not update:
|
||||
return {'record': new_gm, 'created': False, 'updated': False}
|
||||
|
||||
# compare_strains
|
||||
if growth_medium.is_equal(new_gm, exclude_fields=['record_id', 'record_name', 'acronym']):
|
||||
records_are_different = False
|
||||
else:
|
||||
growth_medium.update(new_gm, include_fields=['record_id', 'record_name'])
|
||||
records_are_different = True
|
||||
|
||||
if records_are_different:
|
||||
updated_gm = client.update(GROWTH_MEDIUM_WS, growth_medium)
|
||||
updated = True
|
||||
else:
|
||||
updated_gm = new_gm
|
||||
updated = False
|
||||
return {'record': updated_gm, 'created': False, 'updated': updated}
|
||||
|
||||
|
||||
def get_or_create_growth_medium(client: BiolomicsMirriClient,
|
||||
growth_medium: GrowthMedium):
|
||||
record_name = get_growth_medium_record_name(growth_medium)
|
||||
gm = client.retrieve_by_name(GROWTH_MEDIUM_WS, record_name)
|
||||
if gm is not None:
|
||||
return {'record': gm, 'created': False}
|
||||
|
||||
new_gm = client.create(GROWTH_MEDIUM_WS, growth_medium)
|
||||
return {'record': new_gm, 'created': True}
|
||||
@@ -0,0 +1,122 @@
|
||||
from pprint import pprint
|
||||
import deepdiff
|
||||
|
||||
from mirri.biolomics.remote.biolomics_client import BiolomicsMirriClient, BIBLIOGRAPHY_WS, SEQUENCE_WS, STRAIN_WS
|
||||
|
||||
from mirri.biolomics.serializers.sequence import GenomicSequenceBiolomics
|
||||
from mirri.biolomics.serializers.strain import StrainMirri
|
||||
from mirri.entities.publication import Publication
|
||||
|
||||
|
||||
def retrieve_strain_by_accession_number(client, accession_number):
|
||||
query = {"Query": [{"Index": 0,
|
||||
"FieldName": "Collection accession number",
|
||||
"Operation": "TextExactMatch",
|
||||
"Value": accession_number}],
|
||||
"Expression": "Q0",
|
||||
"DisplayStart": 0,
|
||||
"DisplayLength": 10}
|
||||
|
||||
result = client.search(STRAIN_WS, query=query)
|
||||
total = result["total"]
|
||||
if total == 0:
|
||||
return None
|
||||
elif total == 1:
|
||||
return result["records"][0]
|
||||
else:
|
||||
msg = f"More than one entries for {accession_number} in database"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def get_or_create_publication(client: BiolomicsMirriClient, pub: Publication):
|
||||
new_pub = client.retrieve_by_name(BIBLIOGRAPHY_WS, pub.title)
|
||||
|
||||
if new_pub is not None:
|
||||
return {'record': new_pub, 'created': False}
|
||||
new_pub = client.create(BIBLIOGRAPHY_WS, pub)
|
||||
return {'record': new_pub, 'created': True}
|
||||
|
||||
|
||||
def get_or_create_sequence(client: BiolomicsMirriClient, sequence: GenomicSequenceBiolomics):
|
||||
seq = client.retrieve_by_name(SEQUENCE_WS, sequence.marker_id)
|
||||
if seq is not None:
|
||||
return {'record': seq, 'created': False}
|
||||
|
||||
new_seq = client.create(SEQUENCE_WS, sequence)
|
||||
return {'record': new_seq, 'created': True}
|
||||
|
||||
|
||||
def get_or_create_or_update_strain(client: BiolomicsMirriClient,
|
||||
record: StrainMirri, update=False):
|
||||
response = get_or_create_strain(client, record)
|
||||
new_record = response['record']
|
||||
created = response['created']
|
||||
|
||||
if created:
|
||||
return {'record': new_record, 'created': True, 'updated': False}
|
||||
|
||||
if not update:
|
||||
return {'record': new_record, 'created': False, 'updated': False}
|
||||
|
||||
if record.record_id is None:
|
||||
record.record_id = new_record.record_id
|
||||
if record.record_name is None:
|
||||
record.record_name = new_record.record_name
|
||||
if record.synonyms is None or record.synonyms == []:
|
||||
record.synonyms = new_record.synonyms
|
||||
|
||||
# compare_strains
|
||||
# we exclude pub id as it is an internal reference of pub and can be changed
|
||||
diffs = deepdiff.DeepDiff(new_record.dict(), record.dict(),
|
||||
ignore_order=True, exclude_paths=None,
|
||||
exclude_regex_paths=[r"root\[\'publications\'\]\[\d+\]\[\'id\'\]",
|
||||
r"root\[\'publications\'\]\[\d+\]\[\'RecordId\'\]",
|
||||
r"root\[\'genetics\'\]\[\'Markers\'\]\[\d+\]\[\'RecordId\'\]",
|
||||
r"root\[\'genetics\'\]\[\'Markers\'\]\[\d+\]\[\'RecordName\'\]"])
|
||||
|
||||
if diffs:
|
||||
pprint(diffs, width=200)
|
||||
# pprint('en el que yo mando')
|
||||
# pprint(record.dict())
|
||||
# pprint('lo que hay en db')
|
||||
# pprint(new_record.dict())
|
||||
|
||||
records_are_different = True if diffs else False
|
||||
if records_are_different:
|
||||
updated_record = update_strain(client, record)
|
||||
updated = True
|
||||
else:
|
||||
updated_record = record
|
||||
updated = False
|
||||
return {'record': updated_record, 'created': False, 'updated': updated}
|
||||
|
||||
|
||||
def get_or_create_strain(client: BiolomicsMirriClient, strain: StrainMirri):
|
||||
new_strain = retrieve_strain_by_accession_number(client, strain.id.strain_id)
|
||||
if new_strain is not None:
|
||||
return {'record': new_strain, 'created': False}
|
||||
|
||||
new_strain = create_strain(client, strain)
|
||||
|
||||
return {'record': new_strain, 'created': True}
|
||||
|
||||
|
||||
def create_strain(client: BiolomicsMirriClient, strain: StrainMirri):
|
||||
for pub in strain.publications:
|
||||
creation_response = get_or_create_publication(client, pub)
|
||||
for marker in strain.genetics.markers:
|
||||
creation_response = get_or_create_sequence(client, marker)
|
||||
|
||||
new_strain = client.create(STRAIN_WS, strain)
|
||||
return new_strain
|
||||
|
||||
|
||||
def update_strain(client: BiolomicsMirriClient, strain: StrainMirri):
|
||||
for pub in strain.publications:
|
||||
creation_response = get_or_create_publication(client, pub)
|
||||
for marker in strain.genetics.markers:
|
||||
creation_response = get_or_create_sequence(client, marker)
|
||||
|
||||
new_strain = client.update(STRAIN_WS, strain)
|
||||
return new_strain
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
from mirri.biolomics.remote.endoint_names import (SEQUENCE_WS, STRAIN_WS,
|
||||
GROWTH_MEDIUM_WS, TAXONOMY_WS,
|
||||
COUNTRY_WS, ONTOBIOTOPE_WS,
|
||||
BIBLIOGRAPHY_WS)
|
||||
from mirri.biolomics.remote.rest_client import BiolomicsClient
|
||||
from mirri.biolomics.serializers.sequence import (
|
||||
serialize_to_biolomics as sequence_to_biolomics,
|
||||
serialize_from_biolomics as sequence_from_biolomics)
|
||||
from mirri.biolomics.serializers.strain import (
|
||||
serialize_to_biolomics as strain_to_biolomics,
|
||||
serialize_from_biolomics as strain_from_biolomics)
|
||||
|
||||
from mirri.biolomics.serializers.growth_media import (
|
||||
serialize_to_biolomics as growth_medium_to_biolomics,
|
||||
serialize_from_biolomics as growth_medium_from_biolomics)
|
||||
from mirri.biolomics.serializers.taxonomy import (
|
||||
serialize_from_biolomics as taxonomy_from_biolomics)
|
||||
from mirri.biolomics.serializers.locality import (
|
||||
serialize_from_biolomics as country_from_biolomics)
|
||||
from mirri.biolomics.serializers.ontobiotope import (
|
||||
serialize_from_biolomics as ontobiotope_from_biolomics)
|
||||
from mirri.biolomics.serializers.bibliography import (
|
||||
serializer_from_biolomics as bibliography_from_biolomics,
|
||||
serializer_to_biolomics as bibliography_to_biolomics
|
||||
)
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
class BiolomicsMirriClient:
|
||||
_conf = {
|
||||
SEQUENCE_WS: {
|
||||
'serializers': {'to': sequence_to_biolomics,
|
||||
'from': sequence_from_biolomics},
|
||||
'endpoint': 'WS Sequences'},
|
||||
STRAIN_WS: {
|
||||
'serializers': {'to': strain_to_biolomics,
|
||||
'from': strain_from_biolomics},
|
||||
'endpoint': 'WS Strains'},
|
||||
GROWTH_MEDIUM_WS: {
|
||||
'serializers': {'from': growth_medium_from_biolomics,
|
||||
'to': growth_medium_to_biolomics},
|
||||
'endpoint': 'WS Growth media'},
|
||||
TAXONOMY_WS: {
|
||||
'serializers': {'from': taxonomy_from_biolomics},
|
||||
'endpoint': 'WS Taxonomy'},
|
||||
COUNTRY_WS: {
|
||||
'serializers': {'from': country_from_biolomics},
|
||||
'endpoint': 'WS Locality'},
|
||||
ONTOBIOTOPE_WS: {
|
||||
'serializers': {'from': ontobiotope_from_biolomics},
|
||||
'endpoint': 'WS Ontobiotope'},
|
||||
BIBLIOGRAPHY_WS: {
|
||||
'serializers': {'from': bibliography_from_biolomics,
|
||||
'to': bibliography_to_biolomics},
|
||||
'endpoint': 'WS Bibliography'
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, server_url, api_version, client_id, client_secret, username,
|
||||
password, website_id=1, verbose=False):
|
||||
_client = BiolomicsClient(server_url, api_version, client_id,
|
||||
client_secret, username, password,
|
||||
website_id=website_id, verbose=verbose)
|
||||
|
||||
self.client = _client
|
||||
self.schemas = self.client.get_schemas()
|
||||
self.allowed_fields = self.client.allowed_fields
|
||||
self._transaction_created_ids = None
|
||||
self._in_transaction = False
|
||||
self._verbose = verbose
|
||||
|
||||
def _initialize_transaction_storage(self):
|
||||
if self._in_transaction:
|
||||
msg = 'Can not initialize transaction if already in a transaction'
|
||||
raise RuntimeError(msg)
|
||||
self._transaction_created_ids = []
|
||||
|
||||
def _add_created_to_transaction_storage(self, response, entity_name):
|
||||
if not self._in_transaction:
|
||||
msg = 'Can not add ids to transaction storage if not in a transaction'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
id_ = response.json().get('RecordId', None)
|
||||
if id_ is not None:
|
||||
ws_endpoint_name = self._conf[entity_name]['endpoint']
|
||||
self._transaction_created_ids.insert(0, (ws_endpoint_name, id_))
|
||||
|
||||
def start_transaction(self):
|
||||
self._initialize_transaction_storage()
|
||||
self._in_transaction = True
|
||||
|
||||
def finish_transaction(self):
|
||||
self._in_transaction = False
|
||||
self._transaction_created_ids = None
|
||||
|
||||
def get_endpoint(self, entity_name):
|
||||
return self._conf[entity_name]['endpoint']
|
||||
|
||||
def get_serializers_to(self, entity_name):
|
||||
return self._conf[entity_name]['serializers']['to']
|
||||
|
||||
def get_serializers_from(self, entity_name):
|
||||
return self._conf[entity_name]['serializers']['from']
|
||||
|
||||
def retrieve_by_name(self, entity_name, name):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
serializer_from = self.get_serializers_from(entity_name)
|
||||
response = self.client.find_by_name(endpoint, name=name)
|
||||
if response.status_code == 404:
|
||||
return None
|
||||
elif response.status_code != 200:
|
||||
raise ValueError(f"{response.status_code}: {response.text}")
|
||||
|
||||
ws_entity = response.json()
|
||||
|
||||
return None if ws_entity is None else serializer_from(ws_entity,
|
||||
client=self)
|
||||
|
||||
def retrieve_by_id(self, entity_name, _id):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
serializer_from = self.get_serializers_from(entity_name)
|
||||
response = self.client.retrieve(endpoint, record_id=_id)
|
||||
if response.status_code == 404:
|
||||
return None
|
||||
elif response.status_code != 200:
|
||||
raise ValueError(f"{response.status_code}: {response.text}")
|
||||
|
||||
ws_entity = response.json()
|
||||
|
||||
return serializer_from(ws_entity, client=self)
|
||||
|
||||
def create(self, entity_name, entity):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
serializer_to = self.get_serializers_to(entity_name)
|
||||
serializer_from = self.get_serializers_from(entity_name)
|
||||
data = serializer_to(entity, client=self)
|
||||
response = self.client.create(endpoint, data=data)
|
||||
if response.status_code == 200:
|
||||
if self._in_transaction:
|
||||
self._add_created_to_transaction_storage(response, entity_name)
|
||||
return serializer_from(response.json(), client=self)
|
||||
else:
|
||||
msg = f"return_code: {response.status_code}. msg: {response.json()['errors']['Value']}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def delete_by_id(self, entity_name, record_id):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
response = self.client.delete(endpoint, record_id=record_id)
|
||||
if response.status_code != 200:
|
||||
error = response.json()
|
||||
# msg = f'{error["Title"]: {error["Details"]}}'
|
||||
raise RuntimeError(error)
|
||||
|
||||
def delete_by_name(self, entity_name, record_name):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
response = self.client.find_by_name(endpoint, record_name)
|
||||
if response.status_code != 200:
|
||||
error = response.json()
|
||||
# msg = f'{error["Title"]: {error["Details"]}}'
|
||||
raise RuntimeError(error)
|
||||
try:
|
||||
record_id = response.json()['RecordId']
|
||||
except TypeError:
|
||||
raise ValueError(f'The given record_name {record_name} does not exists')
|
||||
self.delete_by_id(entity_name, record_id=record_id)
|
||||
|
||||
def search(self, entity_name, query):
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
serializer_from = self.get_serializers_from(entity_name)
|
||||
response = self.client.search(endpoint, search_query=query)
|
||||
if response.status_code != 200:
|
||||
error = response.json()
|
||||
# msg = f'{error["Title"]: {error["Details"]}}'
|
||||
raise RuntimeError(error)
|
||||
search_result = response.json()
|
||||
# pprint(search_result)
|
||||
result = {'total': search_result['TotalCount'],
|
||||
'records': [serializer_from(record, client=self)
|
||||
for record in search_result['Records']]}
|
||||
return result
|
||||
|
||||
def update(self, entity_name, entity):
|
||||
record_id = entity.record_id
|
||||
if record_id is None:
|
||||
msg = 'In order to update the record, you need the recordId in the entity'
|
||||
raise ValueError(msg)
|
||||
endpoint = self.get_endpoint(entity_name)
|
||||
serializer_to = self.get_serializers_to(entity_name)
|
||||
serializer_from = self.get_serializers_from(entity_name)
|
||||
data = serializer_to(entity, client=self, update=True)
|
||||
# print('update')
|
||||
# pprint(entity.dict())
|
||||
# print(data)
|
||||
# pprint(data, width=200)
|
||||
response = self.client.update(endpoint, record_id=record_id, data=data)
|
||||
if response.status_code == 200:
|
||||
# print('receive')
|
||||
# pprint(response.json())
|
||||
entity = serializer_from(response.json(), client=self)
|
||||
# pprint(entity.dict())
|
||||
return entity
|
||||
|
||||
else:
|
||||
msg = f"return_code: {response.status_code}. msg: {response.text}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def rollback(self):
|
||||
self._in_transaction = False
|
||||
self.client.rollback(self._transaction_created_ids)
|
||||
self._transaction_created_ids = None
|
||||
@@ -0,0 +1,7 @@
|
||||
SEQUENCE_WS = 'sequence'
|
||||
STRAIN_WS = 'strain'
|
||||
GROWTH_MEDIUM_WS = 'growth_medium'
|
||||
TAXONOMY_WS = 'taxonomy'
|
||||
COUNTRY_WS = 'country'
|
||||
ONTOBIOTOPE_WS = 'ontobiotope'
|
||||
BIBLIOGRAPHY_WS = 'bibliography'
|
||||
@@ -0,0 +1,214 @@
|
||||
import time
|
||||
import re
|
||||
import sys
|
||||
|
||||
import requests
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from oauthlib.oauth2 import LegacyApplicationClient
|
||||
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
|
||||
|
||||
from mirri.entities.strain import ValidationError
|
||||
|
||||
|
||||
class BiolomicsClient:
|
||||
schemas = None
|
||||
allowed_fields = None
|
||||
|
||||
def __init__(self, server_url, api_version, client_id, client_secret,
|
||||
username, password, website_id=1, verbose=False):
|
||||
self._client_id = client_id
|
||||
self._client_secret = client_secret
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._client = None
|
||||
self.server_url = server_url
|
||||
self._api_version = api_version
|
||||
self._auth_url = self.server_url + "/connect/token"
|
||||
self.access_token = None
|
||||
self.website_id = website_id
|
||||
self._verbose = verbose
|
||||
self._schema = self.get_schemas()
|
||||
|
||||
def get_access_token(self):
|
||||
if self._client is None:
|
||||
self._client = LegacyApplicationClient(client_id=self._client_id)
|
||||
authenticated = False
|
||||
else:
|
||||
expires_at = self._client.token["expires_at"]
|
||||
authenticated = expires_at > time.time()
|
||||
if not authenticated:
|
||||
oauth = OAuth2Session(client=self._client)
|
||||
try:
|
||||
token = oauth.fetch_token(
|
||||
token_url=self._auth_url,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
client_id=self._client_id,
|
||||
client_secret=self._client_secret,
|
||||
)
|
||||
except InvalidGrantError:
|
||||
oauth.close()
|
||||
raise
|
||||
self.access_token = token["access_token"]
|
||||
oauth.close()
|
||||
return self.access_token
|
||||
|
||||
def _build_headers(self):
|
||||
self.get_access_token()
|
||||
return {
|
||||
"accept": "application/json",
|
||||
"websiteId": str(self.website_id),
|
||||
"Authorization": f"Bearer {self.access_token}",
|
||||
}
|
||||
|
||||
def get_detail_url(self, end_point, record_id, api_version=None):
|
||||
# api_version = self._api_version if api_version is None else api_version
|
||||
if api_version:
|
||||
return "/".join([self.server_url, api_version, 'data',
|
||||
end_point, str(record_id)])
|
||||
else:
|
||||
return "/".join([self.server_url, 'data', end_point, str(record_id)])
|
||||
|
||||
def get_list_url(self, end_point):
|
||||
return "/".join([self.server_url, 'data', end_point])
|
||||
# return "/".join([self.server_url, self._api_version, 'data', end_point])
|
||||
|
||||
def get_search_url(self, end_point):
|
||||
return "/".join([self.server_url, self._api_version, 'search', end_point])
|
||||
|
||||
def get_find_by_name_url(self, end_point):
|
||||
return "/".join([self.get_search_url(end_point), 'findByName'])
|
||||
|
||||
def search(self, end_point, search_query):
|
||||
self._check_end_point_exists(end_point)
|
||||
header = self._build_headers()
|
||||
url = self.get_search_url(end_point)
|
||||
time0 = time.time()
|
||||
response = requests.post(url, json=search_query, headers=header)
|
||||
time1 = time.time()
|
||||
if self._verbose:
|
||||
sys.stdout.write(f'Search to {end_point} request time for {url}: {time1 - time0}\n')
|
||||
return response
|
||||
|
||||
def retrieve(self, end_point, record_id):
|
||||
self._check_end_point_exists(end_point)
|
||||
header = self._build_headers()
|
||||
url = self.get_detail_url(end_point, record_id, api_version=self._api_version)
|
||||
time0 = time.time()
|
||||
response = requests.get(url, headers=header)
|
||||
time1 = time.time()
|
||||
if self._verbose:
|
||||
sys.stdout.write(f'Get to {end_point} request time for {url}: {time1-time0}\n')
|
||||
return response
|
||||
|
||||
def create(self, end_point, data):
|
||||
self._check_end_point_exists(end_point)
|
||||
self._check_data_consistency(data, self.allowed_fields[end_point])
|
||||
header = self._build_headers()
|
||||
url = self.get_list_url(end_point)
|
||||
return requests.post(url, json=data, headers=header)
|
||||
|
||||
def update(self, end_point, record_id, data):
|
||||
self._check_end_point_exists(end_point)
|
||||
self._check_data_consistency(data, self.allowed_fields[end_point],
|
||||
update=True)
|
||||
header = self._build_headers()
|
||||
url = self.get_detail_url(end_point, record_id=record_id)
|
||||
return requests.put(url, json=data, headers=header)
|
||||
|
||||
def delete(self, end_point, record_id):
|
||||
self._check_end_point_exists(end_point)
|
||||
header = self._build_headers()
|
||||
url = self.get_detail_url(end_point, record_id)
|
||||
return requests.delete(url, headers=header)
|
||||
|
||||
def find_by_name(self, end_point, name):
|
||||
self._check_end_point_exists(end_point)
|
||||
header = self._build_headers()
|
||||
url = self.get_find_by_name_url(end_point)
|
||||
response = requests.get(url, headers=header, params={'name': name})
|
||||
return response
|
||||
|
||||
def get_schemas(self):
|
||||
if self.schemas is None:
|
||||
headers = self._build_headers()
|
||||
url = self.server_url + '/schemas'
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
self.schemas = response.json()
|
||||
else:
|
||||
raise ValueError(f"{response.status_code}: {response.text}")
|
||||
if self.allowed_fields is None:
|
||||
self.allowed_fields = self._process_schema(self.schemas)
|
||||
return self.schemas
|
||||
|
||||
@staticmethod
|
||||
def _process_schema(schemas):
|
||||
schema = schemas[0]
|
||||
allowed_fields = {}
|
||||
for endpoint_schema in schema['TableViews']:
|
||||
endpoint_name = endpoint_schema['TableViewName']
|
||||
endpoint_values = endpoint_schema['ResultFields']
|
||||
fields = {field['title']: field for field in endpoint_values}
|
||||
allowed_fields[endpoint_name] = fields
|
||||
return allowed_fields
|
||||
|
||||
def _check_end_point_exists(self, endpoint):
|
||||
if endpoint not in self.allowed_fields.keys():
|
||||
raise ValueError(f'{endpoint} not a recognised endpoint')
|
||||
|
||||
def _check_data_consistency(self, data, allowed_fields, update=False):
|
||||
update_mandatory = set(['RecordDetails', 'RecordName', 'RecordId'])
|
||||
if update and not update_mandatory.issubset(data.keys()):
|
||||
msg = 'Updating data keys must be RecordDetails, RecordName and RecordId'
|
||||
raise ValidationError(msg)
|
||||
|
||||
if not update and set(data.keys()).difference(['RecordDetails', 'RecordName', 'Acronym']):
|
||||
msg = 'data keys must be RecordDetails and RecordName or Acronym'
|
||||
raise ValidationError(msg)
|
||||
for field_name, field_value in data['RecordDetails'].items():
|
||||
if field_name not in allowed_fields:
|
||||
raise ValidationError(f'{field_name} not in allowed fields')
|
||||
|
||||
field_schema = allowed_fields[field_name]
|
||||
self._check_field_schema(field_name, field_schema, field_value)
|
||||
|
||||
@staticmethod
|
||||
def _check_field_schema(field_name, field_schema, field_value):
|
||||
if field_schema['FieldType'] != field_value['FieldType']:
|
||||
msg = f"Bad FieldType ({field_value['FieldType']}) for {field_name}. "
|
||||
msg += f"It should be {field_schema['FieldType']}"
|
||||
raise ValidationError(msg)
|
||||
|
||||
states = field_schema['states'] if 'states' in field_schema else None
|
||||
if states:
|
||||
states = [re.sub(r" *\(.*\)", "", s) for s in states]
|
||||
|
||||
subfields = field_schema['subfields'] if 'subfields' in field_schema else None
|
||||
if subfields is not None and states is not None:
|
||||
subfield_names = [subfield['SubFieldName']
|
||||
for subfield in subfields if subfield['IsUsed']]
|
||||
|
||||
for val in field_value['Value']:
|
||||
if val['Name'] not in subfield_names:
|
||||
msg = f"{field_name}: {val['Name']} not in {subfield_names}"
|
||||
raise ValidationError(msg)
|
||||
|
||||
if val['Value'] not in states:
|
||||
|
||||
msg = f"{field_value['Value']} not a valid value for "
|
||||
msg += f"{field_name}, Allowed values: {'. '.join(states)}"
|
||||
raise ValidationError(msg)
|
||||
|
||||
elif states is not None:
|
||||
if field_value['Value'] not in states:
|
||||
msg = f"{field_value['Value']} not a valid value for "
|
||||
msg += f"{field_name}, Allowed values: {'. '.join(states)}"
|
||||
raise ValidationError(msg)
|
||||
|
||||
def rollback(self, created_ids):
|
||||
for endpoint, id_ in created_ids:
|
||||
try:
|
||||
self.delete(end_point=endpoint, record_id=id_)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
RECORD_ID = 'RecordId'
|
||||
RECORD_NAME = 'RecordName'
|
||||
RECORD_DETAILS = 'RecordDetails'
|
||||
@@ -0,0 +1,82 @@
|
||||
from typing import List
|
||||
|
||||
from mirri import rgetattr
|
||||
from mirri.entities.publication import Publication
|
||||
from mirri.biolomics.settings import PUB_MIRRI_FIELDS
|
||||
|
||||
RECORD_ID = 'RecordId'
|
||||
RECORD_NAME = 'RecordName'
|
||||
|
||||
PUB_MAPPING = {
|
||||
# 'record_id': 'RecordId',
|
||||
# 'record_name': 'RecordName',
|
||||
'strains': "Associated strains",
|
||||
'taxa': "Associated taxa",
|
||||
'authors': "Authors",
|
||||
# 'sequneces': "Associated sequences",
|
||||
# 'abstract': "Abstract",
|
||||
# 'collection': "Collection",
|
||||
'doi': "DOI number",
|
||||
'editor': "Editor(s)",
|
||||
# 'full_reference': "Full reference",
|
||||
# 'link': "Hyperlink",
|
||||
'isbn': "ISBN",
|
||||
'issn': "ISSN",
|
||||
'issue': "Issue",
|
||||
'journal': "Journal",
|
||||
'journal_book': "Journal-Book",
|
||||
# 'keywords': "Keywords",
|
||||
'first_page': "Page from",
|
||||
'last_page': "Page to",
|
||||
'publisher': "Publisher",
|
||||
'pubmed_id': "PubMed ID",
|
||||
'volume': "Volume",
|
||||
'year': "Year",
|
||||
}
|
||||
REV_PUB_MAPPING = {v: k for k, v in PUB_MAPPING.items()}
|
||||
|
||||
|
||||
def serializer_from_biolomics(ws_data, client=None) -> Publication:
|
||||
pub = Publication()
|
||||
|
||||
pub.record_id = ws_data[RECORD_ID]
|
||||
pub.record_name = ws_data[RECORD_NAME]
|
||||
pub.title = ws_data[RECORD_NAME]
|
||||
for field, value in ws_data['RecordDetails'].items():
|
||||
value = value['Value']
|
||||
if not value:
|
||||
continue
|
||||
attr = REV_PUB_MAPPING.get(field, None)
|
||||
if not attr:
|
||||
continue
|
||||
if attr in ('year', 'first_page', 'last_page'):
|
||||
value = int(value)
|
||||
setattr(pub, attr, value)
|
||||
return pub
|
||||
|
||||
|
||||
def get_publication_record_name(publication):
|
||||
if publication.record_name:
|
||||
return publication.record_name
|
||||
if publication.title:
|
||||
return publication.title
|
||||
if publication.pubmed_id:
|
||||
return f'PUBMED:{publication.pubmed_id}'
|
||||
if publication.doi:
|
||||
return f'DOI:{publication.doi}'
|
||||
|
||||
|
||||
def serializer_to_biolomics(publication: Publication, client=None, update=False):
|
||||
ws_data = {}
|
||||
if publication.record_id:
|
||||
ws_data[RECORD_ID] = publication.record_id
|
||||
ws_data[RECORD_NAME] = get_publication_record_name(publication)
|
||||
details = {}
|
||||
for attr, field in PUB_MAPPING.items():
|
||||
value = getattr(publication, attr, None)
|
||||
if value is None:
|
||||
continue
|
||||
field_type = 'D' if attr == 'year' else "E"
|
||||
details[field] = {'Value': value, 'FieldType': field_type}
|
||||
ws_data['RecordDetails'] = details
|
||||
return ws_data
|
||||
@@ -0,0 +1,66 @@
|
||||
from mirri.biolomics.serializers import RECORD_ID, RECORD_NAME, RECORD_DETAILS
|
||||
from mirri.entities.growth_medium import GrowthMedium
|
||||
|
||||
|
||||
def serialize_from_biolomics(ws_data, client=None) -> GrowthMedium:
|
||||
medium = GrowthMedium()
|
||||
medium.record_name = ws_data.get('RecordName', None)
|
||||
medium.description = get_growth_medium_record_name(medium)
|
||||
medium.record_id = ws_data.get('RecordId', None)
|
||||
for key, value in ws_data['RecordDetails'].items():
|
||||
value = value['Value']
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if key == "Full description":
|
||||
medium.full_description = value
|
||||
if key == "Ingredients":
|
||||
medium.ingredients = value
|
||||
if key == 'Medium description':
|
||||
medium.description = value
|
||||
if key == 'Other name':
|
||||
medium.other_name= value
|
||||
if key == 'pH':
|
||||
medium.ph = value
|
||||
if key == 'Sterilization conditions':
|
||||
medium.sterilization_conditions = value
|
||||
return medium
|
||||
|
||||
|
||||
def get_growth_medium_record_name(growth_medium):
|
||||
if growth_medium.record_name:
|
||||
return growth_medium.record_name
|
||||
if growth_medium.description:
|
||||
return growth_medium.description
|
||||
if growth_medium.acronym:
|
||||
return growth_medium.acronym
|
||||
|
||||
|
||||
GROWTH_MEDIUM_MAPPING = {
|
||||
'acronym': 'Acronym',
|
||||
'full_description': "Full description",
|
||||
'ingredients': "Ingredients",
|
||||
'description': 'Medium description',
|
||||
'other_name': 'Other name',
|
||||
'ph': 'pH',
|
||||
'sterilization_conditions': 'Sterilization conditions'
|
||||
}
|
||||
|
||||
|
||||
def serialize_to_biolomics(growth_medium: GrowthMedium, client=None, update=False):
|
||||
ws_data = {}
|
||||
if growth_medium.record_id:
|
||||
ws_data[RECORD_ID] = growth_medium.record_id
|
||||
record_name = get_growth_medium_record_name(growth_medium)
|
||||
ws_data[RECORD_NAME] = record_name
|
||||
details = {}
|
||||
for field in growth_medium.fields:
|
||||
if field in ('acronym', 'record_id', 'record_name'):
|
||||
continue
|
||||
value = getattr(growth_medium, field, None)
|
||||
if value is not None:
|
||||
details[GROWTH_MEDIUM_MAPPING[field]] = {'Value': value, 'FieldType': 'E'}
|
||||
|
||||
ws_data[RECORD_DETAILS] = details
|
||||
return ws_data
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from mirri.entities.location import Location
|
||||
|
||||
|
||||
def serialize_from_biolomics(ws_data, client=None):
|
||||
return ws_data
|
||||
|
||||
|
||||
# this is a proof of concept
|
||||
def serialize_location(location: Location):
|
||||
fields = {}
|
||||
if location.country:
|
||||
fields['Country'] = {'Value': location.country, 'FieldType': 'E'}
|
||||
if location.latitude and location.longitude:
|
||||
value = {'Latitude': location.latitude,
|
||||
'Longitude': location.longitude}
|
||||
if location.coord_uncertainty:
|
||||
value['Precision'] = location.coord_uncertainty
|
||||
fields['GIS position'] = {'FieldType': 'L', 'Value': value}
|
||||
|
||||
fields['Strains'] = {"FieldType": "RLink", 'Value': [{
|
||||
'Name': {'Value': None, 'FieldType': "E"},
|
||||
'RecordId': None
|
||||
}]}
|
||||
|
||||
return {"RecordDetails": fields,
|
||||
"RecordName": location.country}
|
||||
@@ -0,0 +1,2 @@
|
||||
def serialize_from_biolomics(ws_data, client=None):
|
||||
return ws_data
|
||||
@@ -0,0 +1,81 @@
|
||||
from mirri.entities.sequence import GenomicSequence
|
||||
from mirri.biolomics.serializers import RECORD_ID, RECORD_NAME, RECORD_DETAILS
|
||||
|
||||
|
||||
class GenomicSequenceBiolomics(GenomicSequence):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(freeze=False, **kwargs)
|
||||
|
||||
@property
|
||||
def record_id(self) -> int:
|
||||
return self._data.get(RECORD_ID, None)
|
||||
|
||||
@record_id.setter
|
||||
def record_id(self, value: int):
|
||||
self._data[RECORD_ID] = value
|
||||
|
||||
@property
|
||||
def record_name(self) -> str:
|
||||
return self._data.get(RECORD_NAME, None)
|
||||
|
||||
@record_name.setter
|
||||
def record_name(self, value: str):
|
||||
self._data[RECORD_NAME] = value
|
||||
|
||||
def dict(self):
|
||||
_data = super(GenomicSequenceBiolomics, self).dict()
|
||||
if self.record_id:
|
||||
_data[RECORD_ID] = self.record_id
|
||||
if self.record_name:
|
||||
_data[RECORD_NAME] = self.record_name
|
||||
return _data
|
||||
|
||||
|
||||
def serialize_to_biolomics(marker: GenomicSequenceBiolomics, client=None, update=False):
|
||||
ws_sequence = {}
|
||||
print()
|
||||
if marker.record_id:
|
||||
ws_sequence[RECORD_ID] = marker.record_id
|
||||
if marker.record_name:
|
||||
ws_sequence[RECORD_NAME] = marker.record_name
|
||||
else:
|
||||
ws_sequence[RECORD_NAME] = marker.marker_id
|
||||
details = {}
|
||||
if marker.marker_id:
|
||||
details["INSDC number"] = {"Value": marker.marker_id,
|
||||
"FieldType": "E"}
|
||||
if marker.marker_seq:
|
||||
details["DNA sequence"] = {
|
||||
"Value": {"Sequence": marker.marker_seq},
|
||||
"FieldType": "N"}
|
||||
if marker.marker_type:
|
||||
details['Marker name'] = {"Value": marker.marker_type, "FieldType": "E"}
|
||||
|
||||
ws_sequence[RECORD_DETAILS] = details
|
||||
|
||||
return ws_sequence
|
||||
|
||||
|
||||
MAPPING_WS_SPEC_TYPES = {
|
||||
'Beta tubulin': 'TUBB'
|
||||
}
|
||||
|
||||
|
||||
def serialize_from_biolomics(ws_data, client=None) -> GenomicSequenceBiolomics:
|
||||
marker = GenomicSequenceBiolomics()
|
||||
marker.record_id = ws_data[RECORD_ID]
|
||||
marker.record_name = ws_data[RECORD_NAME]
|
||||
|
||||
for key, value in ws_data['RecordDetails'].items():
|
||||
value = value['Value']
|
||||
if key == 'INSDC number' and value:
|
||||
marker.marker_id = value
|
||||
elif key == 'Marker name' and value:
|
||||
kind = MAPPING_WS_SPEC_TYPES.get(value, None)
|
||||
value = kind if kind else value
|
||||
marker.marker_type = value
|
||||
|
||||
elif key == 'DNA sequence' and 'Sequence' in value and value['Sequence']:
|
||||
marker.marker_seq = value['Sequence']
|
||||
|
||||
return marker
|
||||
@@ -0,0 +1,462 @@
|
||||
import re
|
||||
import sys
|
||||
import pycountry
|
||||
|
||||
from mirri import rgetattr, rsetattr
|
||||
from mirri.entities.date_range import DateRange
|
||||
from mirri.entities.strain import ORG_TYPES, OrganismType, StrainId, StrainMirri, add_taxon_to_strain
|
||||
from mirri.biolomics.remote.endoint_names import (GROWTH_MEDIUM_WS, TAXONOMY_WS,
|
||||
ONTOBIOTOPE_WS, BIBLIOGRAPHY_WS, SEQUENCE_WS, COUNTRY_WS)
|
||||
from mirri.settings import (
|
||||
ALLOWED_FORMS_OF_SUPPLY,
|
||||
NAGOYA_PROBABLY_SCOPE,
|
||||
NAGOYA_NO_RESTRICTIONS,
|
||||
NAGOYA_DOCS_AVAILABLE,
|
||||
NO_RESTRICTION,
|
||||
ONLY_RESEARCH,
|
||||
COMMERCIAL_USE_WITH_AGREEMENT,
|
||||
)
|
||||
from mirri.biolomics.settings import MIRRI_FIELDS
|
||||
from mirri.utils import get_pycountry
|
||||
|
||||
NAGOYA_TRANSLATOR = {
|
||||
NAGOYA_NO_RESTRICTIONS: "no known restrictions under the Nagoya protocol",
|
||||
NAGOYA_DOCS_AVAILABLE: "documents providing proof of legal access and terms of use available at the collection",
|
||||
NAGOYA_PROBABLY_SCOPE: "strain probably in scope, please contact the culture collection",
|
||||
}
|
||||
REV_NAGOYA_TRANSLATOR = {v: k for k, v in NAGOYA_TRANSLATOR.items()}
|
||||
|
||||
RESTRICTION_USE_TRANSLATOR = {
|
||||
NO_RESTRICTION: "no restriction apply",
|
||||
ONLY_RESEARCH: "for research use only",
|
||||
COMMERCIAL_USE_WITH_AGREEMENT: "for commercial development a special agreement is requested",
|
||||
}
|
||||
|
||||
REV_RESTRICTION_USE_TRANSLATOR = {v: k for k,
|
||||
v in RESTRICTION_USE_TRANSLATOR.items()}
|
||||
|
||||
DATE_TYPE_FIELDS = ("Date of collection", "Date of isolation",
|
||||
"Date of deposit", "Date of inclusion in the catalogue")
|
||||
BOOLEAN_TYPE_FIELDS = ("Strain from a registered collection", "Dual use",
|
||||
"Quarantine in Europe", "Interspecific hybrid") # , 'GMO')
|
||||
FILE_TYPE_FIELDS = ("MTA file", "ABS related files")
|
||||
MAX_MIN_TYPE_FIELDS = ("Tested temperature growth range",
|
||||
"Recommended growth temperature")
|
||||
LIST_TYPES_TO_JOIN = ('Other denomination', 'Plasmids collections fields', 'Plasmids')
|
||||
|
||||
MARKER_TYPE_MAPPING = {
|
||||
'16S rRNA': 'Sequences 16s', # or Sequences c16S rRNA
|
||||
'ACT': 'Sequences ACT',
|
||||
'CaM': 'Sequences CaM',
|
||||
'EF-1α': 'Sequences TEF1a',
|
||||
'ITS': 'Sequences ITS',
|
||||
'LSU': 'Sequences LSU',
|
||||
'RPB1': 'Sequences RPB1',
|
||||
'RPB2': 'Sequences RPB2',
|
||||
'TUBB': 'Sequences TUB' # or Sequences Beta tubulin
|
||||
}
|
||||
|
||||
|
||||
def serialize_to_biolomics(strain: StrainMirri, client=None, update=False,
|
||||
log_fhand=None): # sourcery no-metrics
|
||||
if log_fhand is None:
|
||||
log_fhand = sys.stdout
|
||||
strain_record_details = {}
|
||||
|
||||
for field in MIRRI_FIELDS:
|
||||
try:
|
||||
biolomics_field = field["biolomics"]["field"]
|
||||
biolomics_type = field["biolomics"]["type"]
|
||||
except KeyError:
|
||||
# print(f'biolomics not configured: {field["label"]}')
|
||||
continue
|
||||
|
||||
label = field["label"]
|
||||
attribute = field["attribute"]
|
||||
value = rgetattr(strain, attribute, None)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if label == "Accession number":
|
||||
value = f"{strain.id.collection} {strain.id.number}"
|
||||
if label == "Restrictions on use":
|
||||
value = RESTRICTION_USE_TRANSLATOR[value]
|
||||
elif label == "Nagoya protocol restrictions and compliance conditions":
|
||||
value = NAGOYA_TRANSLATOR[value]
|
||||
elif label in FILE_TYPE_FIELDS:
|
||||
value = [{"Name": "link", "Value": fname} for fname in value]
|
||||
elif label == "Other culture collection numbers":
|
||||
value = "; ".join(on.strain_id for on in value) if value else None
|
||||
elif label in BOOLEAN_TYPE_FIELDS:
|
||||
value = 'yes' if value else 'no'
|
||||
elif label in 'GMO':
|
||||
value = 'Yes' if value else 'No'
|
||||
elif label == "Organism type":
|
||||
org_types = [ot.name for ot in value]
|
||||
value = []
|
||||
for ot in ORG_TYPES.keys():
|
||||
is_organism = "yes" if ot in org_types else "no"
|
||||
value.append({"Name": ot, "Value": is_organism})
|
||||
elif label == 'Taxon name':
|
||||
if client:
|
||||
taxa = strain.taxonomy.long_name.split(';')
|
||||
value = []
|
||||
for taxon_name in taxa:
|
||||
taxon = get_remote_rlink(client, TAXONOMY_WS,
|
||||
taxon_name)
|
||||
if taxon:
|
||||
value.append(taxon)
|
||||
if not value:
|
||||
msg = f'WARNING: {strain.taxonomy.long_name} not found in database'
|
||||
log_fhand.write(msg + '\n')
|
||||
# TODO: decide to raise or not if taxon not in MIRRI DB
|
||||
#raise ValueError(msg)
|
||||
|
||||
elif label in DATE_TYPE_FIELDS:
|
||||
year = value._year
|
||||
month = value._month or 1
|
||||
day = value._day or 1
|
||||
if year is None:
|
||||
continue
|
||||
value = f"{year}-{month:02}-{day:02}"
|
||||
elif label == 'History of deposit':
|
||||
value = " < ".join(value)
|
||||
elif label in MAX_MIN_TYPE_FIELDS:
|
||||
if isinstance(value, (int, float, str)):
|
||||
_max, _min = float(value), float(value)
|
||||
else:
|
||||
_max, _min = float(value['max']), float(value['min'])
|
||||
|
||||
content = {"MaxValue": _max, "MinValue": _min,
|
||||
"FieldType": biolomics_type}
|
||||
strain_record_details[biolomics_field] = content
|
||||
continue
|
||||
elif label in LIST_TYPES_TO_JOIN:
|
||||
value = '; '.join(value)
|
||||
# TODO: Check how to deal with crossrefs
|
||||
elif label == "Recommended medium for growth":
|
||||
if client is not None:
|
||||
ref_value = []
|
||||
for medium in value:
|
||||
ws_gm = client.retrieve_by_name(GROWTH_MEDIUM_WS, medium)
|
||||
if ws_gm is None:
|
||||
raise ValueError(
|
||||
f'Can not find the growth medium: {medium}')
|
||||
gm = {"Name": {"Value": medium, "FieldType": "E"},
|
||||
"RecordId": ws_gm.record_id}
|
||||
ref_value.append(gm)
|
||||
value = ref_value
|
||||
else:
|
||||
continue
|
||||
|
||||
elif label == "Form of supply":
|
||||
_value = []
|
||||
for form in ALLOWED_FORMS_OF_SUPPLY:
|
||||
is_form = "yes" if form in value else "no"
|
||||
_value.append({"Name": form, "Value": is_form})
|
||||
value = _value
|
||||
# print(label, value), biolomics_field
|
||||
elif label == "Coordinates of geographic origin":
|
||||
value = {'Latitude': strain.collect.location.latitude,
|
||||
'Longitude': strain.collect.location.longitude}
|
||||
precision = strain.collect.location.coord_uncertainty
|
||||
if precision is not None:
|
||||
value['Precision'] = precision
|
||||
elif label == "Geographic origin":
|
||||
if client is not None and value.country is not None:
|
||||
country = get_pycountry(value.country)
|
||||
if country is None:
|
||||
log_fhand.write(f'WARNING: {value.country} Not a valida country code/name\n')
|
||||
else:
|
||||
_value = get_country_record(country, client)
|
||||
if _value is None: # TODO: Remove this once the countries are added to the DB
|
||||
msg = f'WARNING: {value.country} not in MIRRI DB'
|
||||
log_fhand.write(msg + '\n')
|
||||
#raise ValueError(msg)
|
||||
else:
|
||||
content = {"Value": [_value], "FieldType": "RLink"}
|
||||
strain_record_details['Country'] = content
|
||||
_value = []
|
||||
for sector in ('state', 'municipality', 'site'):
|
||||
sector_val = getattr(value, sector, None)
|
||||
if sector_val:
|
||||
_value.append(sector_val)
|
||||
value = "; ".join(_value) if _value else None
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
elif label == "Ontobiotope":
|
||||
if client and value:
|
||||
onto = get_remote_rlink(client, ONTOBIOTOPE_WS, value)
|
||||
value = [onto] if onto is not None else None
|
||||
elif label == 'Literature':
|
||||
if client and value:
|
||||
pub_rlinks = []
|
||||
for pub in value:
|
||||
rlink = get_remote_rlink(client, BIBLIOGRAPHY_WS, pub.title)
|
||||
if rlink:
|
||||
pub_rlinks.append(rlink)
|
||||
if pub_rlinks:
|
||||
value = pub_rlinks
|
||||
else:
|
||||
continue
|
||||
|
||||
elif label == '':
|
||||
pass
|
||||
|
||||
elif label == 'Ploidy':
|
||||
value = _translate_polidy(value)
|
||||
if value is not None:
|
||||
content = {"Value": value, "FieldType": biolomics_type}
|
||||
strain_record_details[biolomics_field] = content
|
||||
|
||||
# if False:
|
||||
# record_details["Data provided by"] = {
|
||||
# "Value": strain.id.collection, "FieldType": "V"}
|
||||
|
||||
#Markers
|
||||
if client:
|
||||
add_markers_to_strain_details(client, strain, strain_record_details)
|
||||
|
||||
strain_structure = {"RecordDetails": strain_record_details}
|
||||
if update:
|
||||
strain_structure['RecordId'] = strain.record_id
|
||||
strain_structure['RecordName'] = strain.record_name
|
||||
else:
|
||||
strain_structure["Acronym"] = "MIRRI"
|
||||
|
||||
return strain_structure
|
||||
|
||||
|
||||
def add_markers_to_strain_details(client, strain: StrainMirri, details):
|
||||
for marker in strain.genetics.markers:
|
||||
marker_name = marker.marker_id
|
||||
marker_in_ws = client.retrieve_by_name(SEQUENCE_WS, marker_name)
|
||||
if marker_in_ws is None:
|
||||
print('Marker not in web service')
|
||||
continue
|
||||
marker_type = marker.marker_type
|
||||
ws_marker = {
|
||||
"Value": [{
|
||||
"Name": {"Value": marker_in_ws.record_name,
|
||||
"FieldType": "E"},
|
||||
"RecordId": marker_in_ws.record_id
|
||||
}],
|
||||
"FieldType": "NLink"
|
||||
}
|
||||
if marker_in_ws.marker_seq:
|
||||
ws_marker['Value'][0]["TargetFieldValue"] = {
|
||||
"Value": {"Sequence": marker_in_ws.marker_seq},
|
||||
"FieldType": "N"
|
||||
}
|
||||
|
||||
details[MARKER_TYPE_MAPPING[marker_type]] = ws_marker
|
||||
|
||||
|
||||
def get_remote_rlink(client, endpoint, record_name):
|
||||
entity = client.retrieve_by_name(endpoint, record_name)
|
||||
if entity:
|
||||
# some Endpoints does not serialize the json into a python object yet
|
||||
try:
|
||||
record_name = entity.record_name
|
||||
record_id = entity.record_id
|
||||
except AttributeError:
|
||||
record_name = entity["RecordName"]
|
||||
record_id = entity["RecordId"]
|
||||
return {"Name": {"Value": record_name, "FieldType": "E"},
|
||||
"RecordId": record_id}
|
||||
|
||||
|
||||
def add_strain_rlink_to_entity(record, strain_id, strain_name):
|
||||
field_strain = {
|
||||
"FieldType": "RLink",
|
||||
'Value': [{
|
||||
'Name': {'Value': strain_name, 'FieldType': "E"},
|
||||
'RecordId': strain_id
|
||||
}]
|
||||
}
|
||||
record['RecordDetails']['Strains'] = field_strain
|
||||
return record
|
||||
|
||||
|
||||
PLOIDY_TRANSLATOR = {
|
||||
0: 'Aneuploid',
|
||||
1: 'Haploid',
|
||||
2: 'Diploid',
|
||||
3: 'Triploid',
|
||||
4: 'Tetraploid',
|
||||
9: 'Polyploid'
|
||||
}
|
||||
|
||||
REV_PLOIDY_TRANSLATOR = {v: k for k, v in PLOIDY_TRANSLATOR.items()}
|
||||
|
||||
|
||||
def _translate_polidy(ploidy):
|
||||
# print('ploidy in serializer', ploidy)
|
||||
try:
|
||||
ploidy = int(ploidy)
|
||||
except TypeError:
|
||||
return '?'
|
||||
try:
|
||||
ploidy = PLOIDY_TRANSLATOR[ploidy]
|
||||
except KeyError:
|
||||
ploidy = 'Polyploid'
|
||||
return ploidy
|
||||
|
||||
|
||||
def serialize_from_biolomics(biolomics_strain, client=None): # sourcery no-metrics
|
||||
strain = StrainMirri()
|
||||
strain.record_id = biolomics_strain.get('RecordId', None)
|
||||
strain.record_name = biolomics_strain.get('RecordName', None)
|
||||
for field in MIRRI_FIELDS:
|
||||
try:
|
||||
biolomics_field = field["biolomics"]["field"]
|
||||
except KeyError:
|
||||
# print(f'biolomics not configured: {field["label"]}')
|
||||
continue
|
||||
|
||||
label = field["label"]
|
||||
attribute = field["attribute"]
|
||||
field_data = biolomics_strain['RecordDetails'].get(biolomics_field, None)
|
||||
if field_data is None:
|
||||
continue
|
||||
is_empty = field_data.get('IsEmpty')
|
||||
if is_empty:
|
||||
continue
|
||||
if biolomics_field in ('Tested temperature growth range', 'Recommended growth temperature'):
|
||||
value = {'max': field_data.get('MaxValue', None),
|
||||
'min': field_data.get('MinValue', None)}
|
||||
else:
|
||||
value = field_data['Value']
|
||||
# if value in (None, '', [], {}, '?', 'Unknown', 'nan', 'NaN'):
|
||||
# continue
|
||||
|
||||
# print(label, attribute, biolomics_field, value)
|
||||
|
||||
if label == 'Accession number':
|
||||
number = strain.record_name
|
||||
mirri_id = StrainId(number=number)
|
||||
strain.synonyms = [mirri_id]
|
||||
coll, num = value.split(' ', 1)
|
||||
accession_number_id = StrainId(collection=coll, number=num)
|
||||
strain.id = accession_number_id
|
||||
continue
|
||||
elif label == "Restrictions on use":
|
||||
value = REV_RESTRICTION_USE_TRANSLATOR[value]
|
||||
elif label == 'Nagoya protocol restrictions and compliance conditions':
|
||||
value = REV_NAGOYA_TRANSLATOR[value]
|
||||
elif label in FILE_TYPE_FIELDS:
|
||||
value = [f['Value'] for f in value]
|
||||
elif label == "Other culture collection numbers":
|
||||
other_numbers = []
|
||||
for on in value.split(";"):
|
||||
on = on.strip()
|
||||
try:
|
||||
collection, number = on.split(" ", 1)
|
||||
except ValueError:
|
||||
collection = None
|
||||
number = on
|
||||
_id = StrainId(collection=collection, number=number)
|
||||
other_numbers.append(_id)
|
||||
value = other_numbers
|
||||
elif label in BOOLEAN_TYPE_FIELDS:
|
||||
value = value == 'yes'
|
||||
elif label == 'GMO':
|
||||
value = value == 'Yes'
|
||||
elif label == "Organism type":
|
||||
organism_types = [OrganismType(item['Name']) for item in value if item['Value'] == 'yes']
|
||||
if organism_types:
|
||||
value = organism_types
|
||||
elif label in 'Taxon name':
|
||||
value = ";".join([v['Name']['Value'] for v in value])
|
||||
add_taxon_to_strain(strain, value)
|
||||
continue
|
||||
|
||||
elif label in DATE_TYPE_FIELDS:
|
||||
# date_range = DateRange()
|
||||
value = DateRange().strpdate(value)
|
||||
|
||||
elif label in ("Recommended growth temperature",
|
||||
"Tested temperature growth range"):
|
||||
if (value['max'] is None or value['max'] == 0 or
|
||||
value['min'] is None and value['min'] == 0):
|
||||
continue
|
||||
elif label == "Recommended medium for growth":
|
||||
value = [v['Name']['Value'] for v in value]
|
||||
elif label == "Form of supply":
|
||||
value = [item['Name'] for item in value if item['Value'] == 'yes']
|
||||
elif label in LIST_TYPES_TO_JOIN:
|
||||
value = [v.strip() for v in value.split(";")]
|
||||
elif label == "Coordinates of geographic origin":
|
||||
if ('Longitude' in value and 'Latitude' in value and
|
||||
isinstance(value['Longitude'], float) and
|
||||
isinstance(value['Latitude'], float)):
|
||||
strain.collect.location.longitude = value['Longitude']
|
||||
strain.collect.location.latitude = value['Latitude']
|
||||
if value['Precision'] != 0:
|
||||
strain.collect.location.coord_uncertainty = value['Precision']
|
||||
continue
|
||||
elif label == "Altitude of geographic origin":
|
||||
value = float(value)
|
||||
elif label == "Geographic origin":
|
||||
strain.collect.location.site = value
|
||||
continue
|
||||
elif label == 'Ontobiotope':
|
||||
try:
|
||||
value = re.search("(OBT:[0-9]{5,7})", value[0]['Name']['Value']).group()
|
||||
except (KeyError, IndexError, AttributeError):
|
||||
continue
|
||||
|
||||
elif label == 'Ploidy':
|
||||
value = REV_PLOIDY_TRANSLATOR[value]
|
||||
elif label == 'Literature':
|
||||
if client is not None:
|
||||
pubs = []
|
||||
for pub in value:
|
||||
pub = client.retrieve_by_id(BIBLIOGRAPHY_WS, pub['RecordId'])
|
||||
pubs.append(pub)
|
||||
value = pubs
|
||||
|
||||
|
||||
rsetattr(strain, attribute, value)
|
||||
# fields that are not in MIRRI FIELD list
|
||||
# country
|
||||
if 'Country' in biolomics_strain['RecordDetails'] and biolomics_strain['RecordDetails']['Country']:
|
||||
try:
|
||||
country_name = biolomics_strain['RecordDetails']['Country']['Value'][0]['Name']['Value']
|
||||
country = get_pycountry(country_name)
|
||||
country_3 = country.alpha_3 if country else None
|
||||
except (IndexError, KeyError):
|
||||
country_3 = None
|
||||
if country_3:
|
||||
strain.collect.location.country = country_3
|
||||
# Markers:
|
||||
if client:
|
||||
markers = []
|
||||
for marker_type, biolomics_marker in MARKER_TYPE_MAPPING.items():
|
||||
try:
|
||||
marker_value = biolomics_strain['RecordDetails'][biolomics_marker]['Value']
|
||||
except KeyError:
|
||||
continue
|
||||
if not marker_value:
|
||||
continue
|
||||
|
||||
for marker in marker_value:
|
||||
record_id = marker['RecordId']
|
||||
marker = client.retrieve_by_id(SEQUENCE_WS, record_id)
|
||||
if marker is not None:
|
||||
markers.append(marker)
|
||||
if markers:
|
||||
strain.genetics.markers = markers
|
||||
|
||||
return strain
|
||||
|
||||
|
||||
def get_country_record(country, client):
|
||||
for attr in ('common_name', 'name', 'official_name'):
|
||||
val = getattr(country, attr, None)
|
||||
if val is not None:
|
||||
_value = get_remote_rlink(client, COUNTRY_WS, val)
|
||||
if _value is not None:
|
||||
return _value
|
||||
return None
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
from mirri.entities.strain import Taxonomy
|
||||
|
||||
#TODO this is all wrong, needs deep revision
|
||||
|
||||
class TaxonomyMirri(Taxonomy):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(freeze=False, **kwargs)
|
||||
|
||||
fields = ['record_id', 'record_name', 'acronym', 'full_description',
|
||||
'ingredients', 'description', 'other_name', 'ph',
|
||||
'sterilization_conditions']
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._data = {}
|
||||
for field in self.fields:
|
||||
if field in kwargs and kwargs['field'] is not None:
|
||||
value = kwargs['field']
|
||||
setattr(self, field, value)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if attr == '_data':
|
||||
super().__setattr__(attr, value)
|
||||
return
|
||||
if attr not in self.fields:
|
||||
raise TypeError(f'{attr} not an allowed attribute')
|
||||
self._data[attr] = value
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr == '_data':
|
||||
return super
|
||||
if attr not in self.fields and attr != '_data':
|
||||
raise TypeError(f'{attr} not an allowed attribute')
|
||||
return self._data.get(attr, None)
|
||||
|
||||
def dict(self):
|
||||
return self._data
|
||||
|
||||
|
||||
def serialize_from_biolomics(ws_data, client=None) -> TaxonomyMirri:
|
||||
|
||||
return ws_data
|
||||
medium = GrowthMedium()
|
||||
medium.record_name = ws_data.get('RecordName', None)
|
||||
medium.record_id = ws_data.get('RecordId', None)
|
||||
for key, value in ws_data['RecordDetails'].items():
|
||||
value = value['Value']
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if key == "Full description":
|
||||
medium.full_description = value
|
||||
if key == "Ingredients":
|
||||
medium.ingredients = value
|
||||
if key == 'Medium description':
|
||||
medium.description = value
|
||||
if key == 'Other name':
|
||||
medium.other_name= value
|
||||
if key == 'pH':
|
||||
medium.ph = value
|
||||
if key == 'Sterilization conditions':
|
||||
medium.sterilization_conditions = value
|
||||
|
||||
return medium
|
||||
@@ -0,0 +1,373 @@
|
||||
try:
|
||||
from mirri.biolomics.secrets import CLIENT_ID, SECRET_ID, USERNAME, PASSWORD
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'You need a secrets.py in the project dir. with CLIENT_ID, SECRET_ID, USERNAME, PASSWORD')
|
||||
|
||||
MIRRI_FIELDS = [
|
||||
{
|
||||
"attribute": "id",
|
||||
"label": "Accession number",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Collection accession number", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "restriction_on_use",
|
||||
"label": "Restrictions on use",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Restrictions on use", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "nagoya_protocol",
|
||||
"label": "Nagoya protocol restrictions and compliance conditions",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Nagoya protocol restrictions and compliance conditions", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "abs_related_files",
|
||||
"label": "ABS related files",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "ABS related files", "type": "U"},
|
||||
},
|
||||
{
|
||||
"attribute": "mta_files",
|
||||
"label": "MTA file",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "MTA files URL", "type": "U"},
|
||||
},
|
||||
{
|
||||
"attribute": "other_numbers",
|
||||
"label": "Other culture collection numbers",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Other culture collection numbers", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "is_from_registered_collection",
|
||||
"label": "Strain from a registered collection",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Strain from a registered collection", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "risk_group",
|
||||
"label": "Risk Group",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Risk group", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "is_potentially_harmful",
|
||||
"label": "Dual use",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Dual use", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "is_subject_to_quarantine",
|
||||
"label": "Quarantine in Europe",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Quarantine in Europe", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "taxonomy.organism_type",
|
||||
"label": "Organism type",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Organism type", "type": "C"},
|
||||
},
|
||||
{
|
||||
"attribute": "taxonomy.long_name",
|
||||
"label": "Taxon name",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Taxon name", "type": "SynLink"},
|
||||
},
|
||||
{
|
||||
"attribute": "taxonomy.infrasubspecific_name",
|
||||
"label": "Infrasubspecific names",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Infrasubspecific names", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "taxonomy.comments",
|
||||
"label": "Comment on taxonomy",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Comment on taxonomy", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "taxonomy.interspecific_hybrid",
|
||||
"label": "Interspecific hybrid",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Interspecific hybrid", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "status", "label": "Status", "mandatory": False,
|
||||
"biolomics": {"field": "Status", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "history",
|
||||
"label": "History of deposit",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "History", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "deposit.who",
|
||||
"label": "Depositor",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Depositor", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "deposit.date",
|
||||
"label": "Date of deposit",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Deposit date", "type": "H"},
|
||||
},
|
||||
{
|
||||
"attribute": "catalog_inclusion_date",
|
||||
"label": "Date of inclusion in the catalogue",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Date of inclusion in the catalogue", "type": "H"},
|
||||
},
|
||||
{
|
||||
"attribute": "collect.who",
|
||||
"label": "Collected by",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Collector", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "collect.date",
|
||||
"label": "Date of collection",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Collection date", "type": "H"},
|
||||
},
|
||||
{
|
||||
"attribute": "isolation.who",
|
||||
"label": "Isolated by",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Isolator", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "isolation.date",
|
||||
"label": "Date of isolation",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Isolation date", "type": "H"},
|
||||
},
|
||||
{
|
||||
"attribute": "isolation.substrate_host_of_isolation",
|
||||
"label": "Substrate/host of isolation",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Substrate of isolation", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "growth.tested_temp_range",
|
||||
"label": "Tested temperature growth range",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Tested temperature growth range", "type": "S"},
|
||||
},
|
||||
{
|
||||
"attribute": "growth.recommended_temp",
|
||||
"label": "Recommended growth temperature",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Recommended growth temperature", "type": "S"},
|
||||
},
|
||||
{
|
||||
"attribute": "growth.recommended_media",
|
||||
"label": "Recommended medium for growth",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Recommended growth medium", "type": "RLink"},
|
||||
},
|
||||
{
|
||||
"attribute": "form_of_supply",
|
||||
"label": "Form of supply",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Form", "type": "C"},
|
||||
},
|
||||
{
|
||||
"attribute": "other_denominations",
|
||||
"label": "Other denomination",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Other denomination", "type": "E"},
|
||||
},
|
||||
{
|
||||
# here we use latitude to check if there is data in some of the fields
|
||||
"attribute": "collect.location.latitude",
|
||||
"label": "Coordinates of geographic origin",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Coordinates of geographic origin", "type": "L"},
|
||||
},
|
||||
{
|
||||
"attribute": "collect.location.altitude",
|
||||
"label": "Altitude of geographic origin",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Altitude of geographic origin", "type": "D"},
|
||||
},
|
||||
{
|
||||
"attribute": "collect.location",
|
||||
"label": "Geographic origin",
|
||||
"mandatory": True,
|
||||
"biolomics": {"field": "Geographic origin", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "collect.habitat",
|
||||
"label": "Isolation habitat",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Isolation habitat", "type": "E"},
|
||||
},
|
||||
# {
|
||||
# "attribute": "collect.habitat_ontobiotope",
|
||||
# "label": "Ontobiotope term for the isolation habitat",
|
||||
# "mandatory": False,
|
||||
# "biolomics": {"field": "Ontobiotope term for the isolation habitat", "type": "E"},
|
||||
# },
|
||||
{
|
||||
"attribute": "collect.habitat_ontobiotope",
|
||||
"label": "Ontobiotope",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Ontobiotope", "type": "RLink"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.gmo", "label": "GMO", "mandatory": False,
|
||||
"biolomics": {"field": "GMO", "type": "V"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.gmo_construction",
|
||||
"label": "GMO construction information",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "GMO construction information", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.mutant_info",
|
||||
"label": "Mutant information",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Mutant information", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.genotype",
|
||||
"label": "Genotype",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Genotype", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.sexual_state",
|
||||
"label": "Sexual state",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Sexual state", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.ploidy",
|
||||
"label": "Ploidy",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Ploidy", "type": "T"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.plasmids",
|
||||
"label": "Plasmids",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Plasmids", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "genetics.plasmids_in_collections",
|
||||
"label": "Plasmids collections fields",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Plasmids collections fields", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "publications",
|
||||
"label": "Literature",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Literature", "type": "RLink"},
|
||||
},
|
||||
{
|
||||
"attribute": "pathogenicity",
|
||||
"label": "Pathogenicity",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Pathogenicity", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "enzyme_production",
|
||||
"label": "Enzyme production",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Enzyme production", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "production_of_metabolites",
|
||||
"label": "Production of metabolites",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Metabolites production", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "applications",
|
||||
"label": "Applications",
|
||||
"mandatory": False,
|
||||
"biolomics": {"field": "Applications", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "remarks", "label": "Remarks", "mandatory": False,
|
||||
"biolomics": {"field": "Remarks", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "literature_linked_to_the_sequence_genome",
|
||||
"label": "Literature linked to the sequence/genome",
|
||||
"mandatory": False,
|
||||
# "biolomics": {"field": "MTA files URL", "type": "U"},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
PUB_MIRRI_FIELDS = [
|
||||
{
|
||||
"attribute": "pub_id", "mandatory": False,
|
||||
"biolomics": {"field": "", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "pubmed_id", "mandatory": False,
|
||||
"biolomics": {"field": "PubMed ID", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "doi", "mandatory": False,
|
||||
"biolomics": {"field": "DOI number", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "title", "mandatory": False,
|
||||
"biolomics": {"field": "Title", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "authors", "mandatory": False,
|
||||
"biolomics": {"field": "Authors", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "journal", "mandatory": False,
|
||||
"biolomics": {"field": "Journal", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "volumen", "mandatory": False,
|
||||
"biolomics": {"field": "Volume", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "issue", "mandatory": False,
|
||||
"biolomics": {"field": "Issue", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "first_page", "mandatory": False,
|
||||
"biolomics": {"field": "Page from", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "last_page", "mandatory": False,
|
||||
"biolomics": {"field": "Page to", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "last_page", "label": "", "mandatory": False,
|
||||
"biolomics": {"field": "", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "last_page", "label": "", "mandatory": False,
|
||||
"biolomics": {"field": "", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "book_title", "label": "", "mandatory": False,
|
||||
"biolomics": {"field": "Book title", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "publisher", "label": "", "mandatory": False,
|
||||
"biolomics": {"field": "Publisher", "type": "E"},
|
||||
},
|
||||
{
|
||||
"attribute": "editor", "label": "", "mandatory": False,
|
||||
"biolomics": {"field": "Editor(s)", "type": "E"},
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user