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!!!
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