christianhelle / autofaker

Python library designed to minimize the setup/arrange phase of your unit tests
MIT License
6 stars 3 forks source link

Add support for __future__.annotations. #13

Closed jmpcm closed 1 year ago

jmpcm commented 1 year ago

Description

This PR adds support for retrieving the type name, when future.annotations is imported by a user. In the current version of the library, a string is always returned instead of a value of the annotated type. For example, for if the type of an attribute was an int, autofaker would return a string.

Steps to reproduce

The following example, based in the examples in README, can be used to test the PR:

from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from autofaker import Autodata

@dataclass
class DataClass:
    id: int
    first_name: str
    last_name: str
    job: str

data = Autodata.create(DataClass, use_fake_data=True)
print(f'id:     {data.id}')
print(f'name:   {data.first_name} {data.last_name}')
print(f'job:    {data.job}\n')

print(f'anonymous string:    {Autodata.create(str)}')
print(f'anonymous int:       {Autodata.create(int)}')
print(f'anonymous float:     {Autodata.create(float)}')
print(f'anonymous complex:   {Autodata.create(complex)}')
print(f'anonymous range:     {Autodata.create(range)}')
print(f'anonymous bytes:     {Autodata.create(bytes)}')
print(f'anonymous bytearray: {Autodata.create(bytearray)}')
print(f'anonymous datetime:  {Autodata.create(datetime)}')
print(f'anonymous date:      {Autodata.create(datetime.date)}')

Before this PR, the result could be something like

id:     03a9951d-dc2b-45a9-aa99-9233c252fce3
name:   Elizabeth Hernandez
job:    Television production assistant

anonymous string:    dceb353e-9df6-4e8c-85f3-4976027c5abd
anonymous int:       6388
anonymous float:     2024.9613715290293
anonymous complex:   (2407+0j)
anonymous range:     range(0, 4670)
anonymous bytes:     b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABC'
anonymous bytearray: bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9')
anonymous datetime:  2013-01-04 19:05:05.000928
anonymous date:      2026-07-06 00:00:00

The result with the PR applied follows:

id:     5052
name:   Christopher Johnson
job:    Insurance account manager

anonymous string:    e31259d3-a55d-4596-9522-385fbaa91144
anonymous int:       3289
anonymous float:     4630.295375042543
anonymous complex:   (8311+0j)
anonymous range:     range(0, 9442)
anonymous bytes:     b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e'
anonymous bytearray: bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#')
anonymous datetime:  2030-02-28 16:26:19.000608
anonymous date:      2020-02-06 00:00:00

Discussion

I don't know if this is the best solution, but I couldn't find any other option. Feel free to propose any other option that might suit the library better.

I executed all tests and they are all still passing. Yet, I didn't added any new tests, since I don't know how to test this.

Adding support for __future__.annotationsis important, since annotations was not integrated in Python 3.11 (initially is was expected to be integrated in Python 3.10) and there isn't any exception of when it will be [1]. Adding support, enables better experience for the user of the library.

christianhelle commented 1 year ago

@jmpcm first of all thanks for this contribution

I have honestly never tried using __future__ so I'm gonna have to trust you on this one. As long as all existing tests still pass then I don't see any reason to not merge this in

This will also force me to learn about __future__ 😄

Any objections from your side @marksmayo?

jmpcm commented 1 year ago

@christianhelle You're welcome! This is a very interesting library. My needs are the same of what you describe in your blog post. Really hope this project keeps growing ;)

I forgot to write the case that started it all :sweat_smile: Usually I use annotations when 1) I want to write the return type in a class method, or 2) if there is a forward declaration of a class (I try to avoid this last case). The case that started it all was 1. Example:

from __future__ import annotations
from dataclasses import dataclass
from autofaker import Autodata

@dataclass
class Device:
    id: int
    type: str
    battery: str

    @classmethod
    def from_json(cls, value:str) -> Device:
        pass

If you don't import annotations, the function definition will have to be written as def from_json(cls, value:str) -> "Device": (note the double commas around Device). This is because the type Device is still not defined when the function is declared. the purpose of annotations (and it is something to be supported in the future) is to avoid placing the commas, because a type is, well, a type :)

christianhelle commented 1 year ago

@christianhelle You're welcome! This is a very interesting library. My needs are the same of what you describe in your blog post. Really hope this project keeps growing ;)

Thank you for your kind words and I'm glad that you find it interesting

Coming from a .NET background, when I started working with Python I immediately missed the unit testing tooling that I was used to so I had to build it myself. It was also a good learning exercise as I was very new to Python when I started on this

christianhelle commented 1 year ago

@jmpcm this contribution is now released to PyPi as v1.0.18

jmpcm commented 1 year ago

Great! Thank you 😄