Skip to content

Instantly share code, notes, and snippets.

@dav009
Created April 8, 2021 22:45
Show Gist options
  • Save dav009/12a62e6855fea23fb525a6c9d509b9db to your computer and use it in GitHub Desktop.
Save dav009/12a62e6855fea23fb525a6c9d509b9db to your computer and use it in GitHub Desktop.
"""
Ejercicio QR Text Parse
Un codigo QR puede contener texto plano, como el siguiente:
000201010212501127288632702512600220290023010000050176943520460125303032540510.005802AR2611ar.com.vale5914Tamara Bernath6012Buenos Aires621008000502016304bbd0
Y en este caso, el texto tiene una codificacion que sigue un formato especifico.
El texto esta formado por paquetes sucesivos, que a su vez estan formados por 3 unidades:
- Numero de campo (n): los primeros 2 bytes
- Largo del campo (L): los siguientes 2 bytes
- Contenido del campo: los siguientes L bytes
Aplicando esto al texto de ejemplo, obtendriamos que el primer paquete es:
package = 000201
n = 00
L = 02
content = 01
El segundo:
package = 010212
n = 01
L = 02
content = 12
El tercero:
package = 501127288632702
n = 50
L = 11
content = 27288632702
Y asi sucesivamente.
El ejercicio consiste en desarrollar un script que tenga por entrada un texto plano como el del ejemplo,
y su salida sea una descripcion de los packages que contenga la entrada (numero, largo y contenido).
"""
class QRTextParse:
qr_code = None
packages = None
def __init__(self, qr_code, packages=None):
self.qr_code = qr_code
self.packages = packages
def print_packages(self):
if isinstance(self.packages, list):
for package in self.packages:
print(f'Número de campo: {package[0]} Longitud de campo: {package[1]} Contenido: {package[2]}')
def get_packages(self):
if not self.packages:
self.packages = list()
while len(self.qr_code) > 0:
field_number = self.qr_code[:2]
field_long = self.qr_code[2:4]
try:
L_bytes = int(field_long) + 4
except ValueError:
return 'The QR Code has not a valid format.'
field_content = self.qr_code[4:L_bytes]
self.qr_code = self.qr_code[L_bytes:]
# construct package
package = [field_number, field_long, field_content]
self.packages.append(package)
return self.packages
def decoder(self):
if isinstance(self.qr_code, str) and len(self.qr_code) != 0:
return self.get_packages()
else:
return 'The QR Code is invalid.'
if __name__ == '__main__':
sample_qr = '000201010212501127288632702512600220290023010000050176943520460125303032540510.005802AR2611ar.com.vale5914Tamara Bernath6012Buenos Aires621008000502016304bbd0'
my_qr = QRTextParse(qr_code=sample_qr)
my_qr.decoder()
my_qr.print_packages()
@dav009
Copy link
Author

dav009 commented Apr 8, 2021

def decoder(..) # no me gusta el nombre de esta funcion. los nombres de funciones usualmente son verbos. `decode`
def get_packages() # esta es una funcion que deba usar el usuario final? deberia ser privada ?  .
# es un nit pero `get` me parece poco pythonico, preferia algo como `def __packages__`
def print_packages
# no me gusta esta fnucion, realmente no importa mucho
# preferia que esta clase se enfocara en la estructura de datos. la responsabilidad de esta clase es parsear. 
# imprimir o no es lo que el usuario hace cn el resultado de parsear el qr code

#en. este try/except, pienso que este objeto estaria a nivel de estructura de datos que otro programador usaria, y por lo tanto preferiria darle la excepcion al que esta llamando a este objeto.
# esta parte es la que menos me gusta porque tu funcion puede retornar dos cosas:
# 1. una lista de listas ([field_number, field_long..]) 
# 2. un string
# si usara tu estructura de datos y quisiera saber que fallo porque el qr esta malo, tendria que chequear que retorno un string, eso se ve poco elegante.
            try:
                L_bytes = int(field_long) + 4
            except ValueError:
                return 'The QR Code has not a valid format.'

# en este caso preferiria lanzar la excepcion o si quiero ser super elegante
#  hacer mi propia excepcion con un mensaje adicional
# asi mi funcion solo retornaria un solo tipo : lista de listas, con posibilidad de lanzar una excepcion.

# otra alternativa, menos elegante es que hagas que tu funcion retorne un Optional[List] asi seria mas claro para el que llame a esta funcion.

from typing import Optional
def get_packages(..) -> Optional[List]
 . ...
            try:
                L_bytes = int(field_long) + 4
            except ValueError:
                return None
....  
           

como lo haria yo?

no estoy chequeando la correctitud. mayormente preferencias.

# qr.py

from dataclasses import dataclass

@dataclass
class Package:
    field_number: int
    field_long: int
    field_content: str

def validate(qr_code):
   pass

# prefiero la programacion funcional por eso preferi hacer un qr.py con una funcion
# en tu propuesta el usuario final de tu libreria tiene que saber que existe un objeto/clase QRparser 
# tinee que crear el objeto QRparser, luego ver que funciion llamar , decidir entre llamar la funcion `decoder` o `get_packages`
# en mi implementacion el usuario solo tiene que importar una funcion y allamarla. 
# en mi caso el usuario tiene que elegir entre `validate ` y `parse` y me da la impresion que es mas claro cual elegir(?): parse
# en tu alternativa tu retorna una lista de listas, pero es confuso para el usuario final cuando lee `package[0]`  (que campo es este? el content? el long? 
# en mi alternativa `package.field_number` se leeria mucho mas claro 

def parse(qr_code) -> Generator[Package, None, None]:
  # la logica que escribiste pero en vez de retornar una lista
  # pondria a retornar un iterador para 'mostrar que se iteradores ' . 
 # no se si sea la mejor justificacion
  
  while (...):
        .. <la logica de tu metodo get_packages aqui, con unos cambios> 
        ..
        yield Package(field_number, field_long, field_content)
          

# main.py
from qr import parse

for package in parse('00120.....'):
     print(package)


   

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