Skip to content
This repository
tree: 4b3deb8438
Fetching contributors…

Cannot retrieve contributors at this time

executable file 392 lines (344 sloc) 15.231 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''Cliente que acessa o banco de dados do ITIS (http://www.itis.gov/).

Baseado no nome do táxon ele busca a entrada para descobrir o nome do táxon pai
e o ranking (Reino, Filo, Classe...).

Algoritmo:
- Abre URL através da API do ITIS e salva o XML, se houver (get_tsn)
- Usa ElementTree pra parse o XML a partir da raiz
- Busca as tags "unitName1" e "tsn" e salva em lista
- Se houver mais de 1 ou nenhum emitir aviso
- Do contrário, procurar pai do primeiro elemento (get_parent)
- Usa novamente API do ITIS para buscar pai pelo TSN
- Se existir, descobrir seu ranqueamento e se é icônico (get_rank)
- Caso não seja, buscar pelo pai do pai (get_parent) até primeira categoria icônica
- Achado o táxon é necessário traduzir (translate) e salvar no objeto
'''

from suds.client import Client
import logging

# Django environment import
from django.core.management import setup_environ
import settings
setup_environ(settings)
from meta.models import Taxon

# Criando o logger.
logger = logging.getLogger('itis')
logger.setLevel(logging.DEBUG)
logger.propagate = False
# Define formato das mensagens.
formatter = logging.Formatter('[%(levelname)s] %(asctime)s @ %(module)s %(funcName)s (l%(lineno)d): %(message)s')

# Cria o manipulador do console.
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# Define a formatação para o console.
console_handler.setFormatter(formatter)
# Adiciona o console para o logger.
logger.addHandler(console_handler)

# Cria o manipulador do arquivo.log.
#file_handler = logging.FileHandler('logs/itis.log')
#file_handler.setLevel(logging.DEBUG)
## Define a formatação para o arquivo.log.
#file_handler.setFormatter(formatter)
## Adiciona o arquivo.log para o logger.
#logger.addHandler(file_handler)


class Itis:
    '''Interação com o ITIS'''
    def __init__(self, query):
        logger.info('Iniciando contato com ITIS.')

        self.url = 'http://www.itis.gov/ITISWebService.xml'
        try:
            self.client = Client(self.url)
        except:
            print u'Não conseguiu conectar o cliente!'

        self.query = query
        self.name = u''
        self.tsn = None
        self.rank = u''
        self.valid = False
        self.parent = {}
        self.parents = []

        # Busca e identifica táxon pelo nome e tsn.
        taxon_data = self.parse_results(self.query)
        #XXX Melhorar isso...
        # Popula dados.
        if taxon_data:
            self.name = taxon_data['name']
            self.tsn = taxon_data['tsn']
            self.valid = taxon_data['valid']

        # Tenta ao menos encontrar o gênero.
        if not self.tsn:
            self.search_genus()
        else:
            # Se tiver encontrado algum TSN.
            # Checa se táxon é válido (XXX necessário?).
            if not self.valid:
                self.valid = self.is_valid(self.tsn)
            if self.valid:
                # Se for válido, pegar hierarquia.
                tree_data = self.parse_hierarchy(self.tsn)
                if tree_data:
                    self.rank = tree_data['rank']
                    self.parent['name'] = tree_data['parent_name']
                    self.parent['tsn'] = tree_data['parent_tsn']
                    self.parents = tree_data['parents']
                    # Remove o último item, referente ao próprio táxon.
                    # Deixa somente os parents mesmo.
                    self.parents.pop()

                    # Traduz para português.
                    for taxon in self.parents:
                        taxon.rankName = self.translate(taxon.rankName)
                        taxon.taxonName = unicode(taxon.taxonName)
                        taxon.tsn = int(taxon.tsn)
                        if taxon.parentName and taxon.parentTsn:
                            taxon.parentName = unicode(taxon.parentName)
                            taxon.parentTsn = int(taxon.parentTsn)


    def translate(self, rank):
        '''Traduz nome do ranking pra português.'''

        en2pt = {
                'Subform': u'Subforma',
                'Superorder': u'Superordem',
                'Variety': u'Variedade',
                'Infraorder': u'Infraordem',
                'Section': u'Seção',
                'Subclass': u'Subclasse',
                'Subsection': u'Subseção',
                'Kingdom': u'Reino',
                'Division': u'Divisão',
                'Subtribe': u'Subtribo',
                'Aberration': u'Aberração',
                'InfraOrder': u'Infraordem',
                'Subkingdom': u'Subreino',
                'Infraclass': u'Infraclasse',
                'Subfamily': u'Subfamília',
                'Class': u'Classe',
                'Superfamily': u'Superfamília',
                'Subdivision': u'Subdivisão',
                'Morph': u'Morfotipo',
                'Race': u'Raça',
                'Unspecified': u'Não especificado',
                'Suborder': u'Subordem',
                'Genus': u'Gênero',
                'Order': u'Ordem',
                'Subvariety': u'Subvariedade',
                'Tribe': u'Tribo',
                'Subgenus': u'Subgênero',
                'Form': u'Forma',
                'Family': u'Família',
                'Subphylum': u'Subfilo',
                'Stirp': u'Estirpe',
                'Phylum': u'Filo',
                'Superclass': u'Superclasse',
                'Subspecies': u'Subespécie',
                'Species': u'Espécie',
                }

        rank_pt = en2pt[rank]

        if rank_pt:
            return rank_pt
        else:
            return rank

    def search_by_scientific_name(self, query, attempt=0):
        '''Busca nome científico no ITIS.

Função é um wrapper para searchByScientificName method:
http://www.itis.gov/ws_searchApiDescription.html#SrchBySciName

Exemplo do XML para a busca "Crustacea":

http://www.itis.gov/ITISWebService/services/ITISService/searchByScientificName?srchKey=Crustacea

<ns:searchByScientificNameResponse>
<ns:return type="org.usgs.itis.itis_service.data.SvcScientificNameList">
<ax23:scientificNames type="org.usgs.itis.itis_service.data.SvcScientificName">
<ax23:combinedName>Crustacea</ax23:combinedName>
<ax23:unitInd1/>
<ax23:unitInd2/>
<ax23:unitInd3/>
<ax23:unitInd4/>
<ax23:unitName1>Crustacea</ax23:unitName1>
<ax23:unitName2/>
<ax23:unitName3/>
<ax23:unitName4/>
<ax23:tsn>83677</ax23:tsn>
</ax23:scientificNames>

<ax23:scientificNames type="org.usgs.itis.itis_service.data.SvcScientificName">
<ax23:combinedName>Schizoporella crustacea</ax23:combinedName>
<ax23:unitInd1/>
<ax23:unitInd2/>
<ax23:unitInd3/>
<ax23:unitInd4/>
<ax23:unitName1>Schizoporella</ax23:unitName1>
<ax23:unitName2>crustacea</ax23:unitName2>
<ax23:unitName3/>
<ax23:unitName4/>
<ax23:tsn>156303</ax23:tsn>
</ax23:scientificNames>
</ns:return>
</ns:searchByScientificNameResponse>

'''
        logger.info('Procurando TSN de %s...', query)

        # Tenta executar a busca usando o query. Caso a conexão falhe, tenta
        # novamente.
        try:
            results = self.client.service.searchByScientificName(query)
        except:
            while attempt < 3:
                logger.warning('Não conseguiu conectar... tentativa=%d' % attempt)
                attempt += 1
                self.search_by_scientific_name(query, attempt)
            logger.critical('Terminando conexão. Não está rolando.')
            results = None
        return results

    def parse_results(self, query):
        '''Manuseia resultados da busca.'''
        # Objeto que guarda dados e é retornado.
        data = {}

        # Busca no Itis.
        results = self.search_by_scientific_name(query)

        #FIXME Se ele não consegue se conectar no Itis os results vem vazios e
        #dá erro!
        # Le os resultados para encontrar o táxon.
        if results.scientificNames:
            # Se houver mais de 1 resultado, comparar valores.
            if len(results.scientificNames) > 1:
                logger.debug('Mais de um táxon encontrado!')
                theone = []
                # Tentando encontrar o táxon certo.
                for entry in results.scientificNames:
                    print entry.combinedName
                    if self.fix(entry.combinedName) == query:
                        theone.append(entry)
                if len(theone) == 1:
                    taxon = theone[0]
                    data['name'] = self.fix(taxon.combinedName)
                    data['tsn'] = taxon.tsn
                    data['valid'] = self.is_valid(taxon.tsn)
                    logger.info('Táxon com nome exato encontrado: %s',
                            data['name'])
                elif len(theone) > 1:
                    # Checa qual destes táxons é válido.
                    for entry in theone:
                        valid = self.is_valid(entry.tsn)
                        # Quando não há nomes alternativos, o táxon é válido.
                        if valid:
                            data['name'] = self.fix(entry.combinedName)
                            data['tsn'] = entry.tsn
                            data['valid'] = True
                            logger.info('Táxon válido encontrado: %s',
                                    data['name'])
                            # Assume que só existe 1 táxon oficialmente aceito.
                            #TODO Verificar isso...
                            break
                    else:
                        logger.warning('Nenhum táxon com nome %s é válido.', query)
                else:
                    logger.info('Nenhum táxon com o nome exato %s foi encontrado.', query)
                    #TODO busca pelo gênero.
            else:
                taxon = results.scientificNames[0]
                data['name'] = self.fix(taxon.combinedName)
                data['tsn'] = taxon.tsn
                data['valid'] = self.is_valid(taxon.tsn)
        else:
            logger.info('Nenhum táxon com o nome de %s foi encontrado.', query)
            #TODO busca pelo gênero.
        return data

    def fix(self, combinedName):
        '''Arruma combinedName do ITIS.'''
        fixed = ' '.join(combinedName.split())
        return fixed

    def is_valid(self, tsn):
        '''Checa se táxon é válido e retorna BOOL.'''
        #TODO Retorna o tsn da sinonímia???
        response = self.get_accepted_names_from_tsn(tsn)
        # Quando não há nomes alternativos, o táxon é válido.
        if response.acceptedNames == []:
            return True
        else:
            return False

    def get_accepted_names_from_tsn(self, tsn):
        '''Entre táxons com mesmo nome confere qual TSNs é válido.'''
        try:
            response = self.client.service.getAcceptedNamesFromTSN(tsn)
        except:
            logger.warning('Problema na conexão para checar a validade de %d', tsn)
            response = None
        return response

    def get_full_hierarchy(self, tsn):
        '''Encontra hierarquia e converte valores.

Usa: http://www.itis.gov/ws_hierApiDescription.html#getFullHierarchy

O formato vindo do SOAP não serve para criar as instâncias no Django.
Por isso é necessário converter em unicode e int, além de traduzir os
rankings.

Exemplo:
http://www.itis.gov/ITISWebService/services/ITISService/getFullHierarchyFromTSN?tsn=1378
'''
        #XXX Garantir que o táxon é válido antes de puxar a hierarquia.
        logger.info('Pegando hierarquia...')

        # Tenta se conectar.
        try:
            hierarchy = self.client.service.getFullHierarchyFromTSN(tsn)
        except:
            logger.warning('Erro ao puxar a hierarquia de %s, problema na conexão', tsn)
            hierarchy = None
        return hierarchy

    def parse_hierarchy(self, tsn):
        '''Le e retorna hierarquia.'''
        # Data.
        data = {}

        # Pega hierarquia.
        hierarchy = self.get_full_hierarchy(tsn)

        # Processa.
        if not hierarchy.hierarchyList:
            # Hierarquia vazia só é Reino para táxons válidos.
            # Apenas busca hierarquia de táxons válidos.
            data['rank'] = self.translate(u'Kingdom')
            data['parents'] = hierarchy.hierarchyList
        else:
            # Último ítem é o táxon em questão.
            taxon = hierarchy.hierarchyList[-1]
            # Salva propriedades.
            data['rank'] = self.translate(taxon.rankName)
            data['parent_name'] = unicode(taxon.parentName)
            data['parent_tsn'] = int(taxon.parentTsn)
            data['parents'] = hierarchy.hierarchyList
        return data

    def search_genus(self):
        '''Search ITIS for the genus, if possible.'''
        split_query = self.query.split()
        if len(split_query) > 1:
            pseudo_genus = split_query[0]
            genus_data = self.parse_results(pseudo_genus)
            if genus_data['valid']:
                self.name = self.query
                self.rank = u'Espécie'
                self.parent['name'] = genus_data['name']
                self.parent['tsn'] = genus_data['tsn']
                tree_data = self.parse_hierarchy(genus_data['tsn'])
                self.parents = tree_data['parents']

    def update_model(self, taxon):
        '''Update database table with newly fetched records.'''
        if not self.name:
            logger.critical('No taxon is defined! Aborting...')

        #XXX Melhor se perguntar se o táxon é válido, ou não?

        # Update parents.
        if self.parents:
            for parent in self.parents:
                logger.debug('Atualizando %s...', parent.taxonName)
                above, new = Taxon.objects.get_or_create(name=parent.taxonName)
                above.rank = parent.rankName
                above.tsn = parent.tsn
                if parent.parentName:
                    above.parent = Taxon.objects.get(name=parent.parentName)
                above.save()
                logger.debug('%s salvo!', above.name)

        # Atualiza o táxon em questão.
        logger.debug('Atualizando %s...', self.name)
        taxon.rank = self.rank
        taxon.tsn = self.tsn
        if self.parent:
            taxon.parent = Taxon.objects.get(name=self.parent['name'])
        taxon.save()
        logger.debug('%s salvo!', self.name)
        return taxon

def main():
    pass

# Início do programa
if __name__ == '__main__':
    # Inicia.
    main()
Something went wrong with that request. Please try again.