Skip to content

Instantly share code, notes, and snippets.

@FelipeSBarros
Last active April 25, 2022 14:59
Show Gist options
  • Save FelipeSBarros/5aaa36792e44665df0de67a6a5a789a2 to your computer and use it in GitHub Desktop.
Save FelipeSBarros/5aaa36792e44665df0de67a6a5a789a2 to your computer and use it in GitHub Desktop.
Solução_final

Problema solucionado. Veja no fim do documento a solução

Pessoall, bom dia!! Estive quebrando a cabeça com um ItemValidation... Será que voces podem ajudar?

Estou trabalhando com o scrapy para raspagem de dados. Estou raspando um item que é um horário, que está com o padrão "9h30", por exemplo. Então no Items, criei uma função para converter a datetime.time, caso haja algum dado, caso contrário retorna None:

def convert_time(text):
    return datetime.strptime(text, "%Hh%M").time() if text else None

E no items, passo ele pelo MapCompose:

hora_inicio = scrapy.Field(
        input_processor=MapCompose(convert_time), output_processor=TakeFirst()
    )

Até aqui, tudo bem: Pego meu dado e converto ele para datetime.time...

No validators (Spidermon), criei uma validação personalizada, herdando de BaseType e valido se a clase do dado é datetime.time:

class TimeType(BaseType):
    def validate_time(self, value):
        if type(value) != time:
            raise ValidationError("Not datetime.time class")
        return value

Em models esse campo está como Time:

hora_inicio = Column(Time())

**E aí começaram as minhas dúvidas... Eu estou recebendo erro de validação para o campo hora_inicio e hora_final (que possuem as mesmas características). Então, coloquei alguns "prints" no processo de validação e ví que o Value que recebo para validação é a hora em formato e texto: "09:30:00" e, logo da classe str.

Ainda que o dado esteja como str se nota que passou pelo input/output processors do meu ItemLoader, já que a estrutura está com o padrão datetime.time aidna que a classe esteja como str....

E a minha dúvida é: por esse dato ter passado pelo ItemLoader, não deveria receber-lo como um objeto da classe datetime.time?

E o mais doido é que ao adicionar os erros nos items, consigo ver que o dado, ainda que esteja acusando erro de validação, está sendo reconhecido, logo em seguida, como detime.time:

{'_validation': defaultdict(<class 'list'>,
                            {'hora_fim': ['Not datetime.time class'],
                             'hora_inicio': ['Not datetime.time class']}),
...
 
 'hora_fim': datetime.time(18, 0),
 'hora_inicio': datetime.time(9, 0),
}

Ainda que eu acredite que a ordem dos pipelines nãoa fete a quesao levantada, comento que o meu itemspipeline está com a seguinte ordem:

"ITEM_PIPELINES": {
            "spidermon.contrib.scrapy.pipelines.ItemValidationPipeline": 100,
            "agenda_transparente.pipelines.DropDuplicatedAppoitmentPipeline": 200,
            "agenda_transparente.pipelines.SaveAppoitmentPipeline": 300,
        },

Primeiro teste: Confirmar se o input/output processors estão funcionando e retornando o dado na classe datetime.time:

>>>from itemloaders.processors import TakeFirst, MapCompose


>>>def convert_time(text):
...    return datetime.strptime(text, "%Hh%M").time() if text else None

>>>hora_inicio = ['9h30']

>>>input_processor = MapCompose(convert_time)
>>>input_processor(hora_inicio)
[datetime.time(9, 30)]

>>>output_processor=TakeFirst()
>>>output_processor(input_processor(hora_inicio))
datetime.time(9, 30)

O resultado, mesmo com o TakeFirst(), é um objeto da classe datetime.time...

Testando todo o fluxo:

>>>from datetime import time
>>>from agenda_transparente.items import AgendaItem
>>>from scrapy.loader import ItemLoader
>>>loader = ItemLoader(item=AgendaItem(), selector=None)
>>>loader.add_value("hora_inicio", '9h30')
>>>loader.load_item()
{'hora_inicio': datetime.time(9, 30)}

>>>item = loader.load_item()
>>>item.get('hora_inicio')
datetime.time(9, 30)
>>>type(item)
<class 'agenda_transparente.items.AgendaItem'>

>>>def validate_time(value):
...    print(type(value))
...    print(value)
...    if type(value) != time:
...        raise ValidationError(f"Not datetime.time class. Class type {type(value)}")
...    return value

>>>validate_time(item.get('hora_inicio'))
<class 'datetime.time'>
09:30:00
datetime.time(9, 30)

Qualquer colaboração é bem vinda!!!

Solução

Percebi com a ajuda do @cuducos que o BaseType converte o objeto recebido como instancia str. É por este motivo que o BaseType possui alguns métodos, como to_primitive e to_native. No caso eu precisei sobreescrever o método to_ŕimitive, fazendo com que o objeto seja convertido a datetime.time, novamente e o usei antes da validação. Sicando da seguinte forma:

class TimeType(BaseType):
    def to_primitive(self, value, context=None):
        value = datetime.strptime(value, "%H:%M:%S").time()
        return value

    def validate_time(self, value, context=None):
        value = self.to_primitive(value)
        if not isinstance(value, time):
            raise ValidationError("Not datetime.time class")

        return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment