FactoryBoy / factory_boy

A test fixtures replacement for Python
https://factoryboy.readthedocs.io/
MIT License
3.48k stars 392 forks source link

Add Dynamodb ORM Factory #776

Open ezbc opened 4 years ago

ezbc commented 4 years ago

The problem

I haven't found support for creating a factory with the Dynamodb ORM pynamodb. Sometimes I use a django-supported ORM for which the DjangoModelFactory works great, and sometimes I need a NoSQL DB.

Proposed solution

I assume this would include implementing the base.Factory interface, though I'm pretty unfamiliar with what's under the hood of factory_boy.

Edit: ORM (Object Relational Mapping) for a NoSQL DB is a misnomer :-P ONSQLM (Object NoSQL Mapping) would be more appropriate

rbarrois commented 3 years ago

Hi, and thanks for the suggestion!

It brings two subjects to mind: explaining how this base class can be built; and deciding where that code should live.

Coding the PynamoModelFactory class

The simplest way is to take a look at the MogoFactory or MongoEngineFactory.

The first step is to create a subclass of base.Factory, mark it as abstract, and override its _build and _create functions:

class PynamoModelFactory(base.Factory):
    class Meta:
        abstract = True

    @classmethod
    def _build(cls, model_class, *args, **kwargs):
        return model_class.some_builder(*args, **kwargs)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        # Assuming the `.create()` is actually build+save
        obj = cls._build(model_class, *args, **kwargs)
        obj.save()
        return obj

If the mapper needs additional settings (for instance emulating the django_get_or_create part), or if you want advanced syntax to find the model, you'll have to add a custom FactoryOptions subclass (cf. DjangoOptions), and attach it at YourFactory._options_class.

Hosting the code

Including factory classes for other ODMs (Object Database Mapper?) within FactoryBoy's codebase is useful to end users; however, it requires the maintainers of the project to have some insight as to how that DB adapter actually works — for instance, MogoFactory or MongoEngineFactory don't provide the same amount of features as DjangoModelFactory or SQLAlchemyModelFactory.

For this PynamoFactory, once the code exists, I see two options:

What do you think?

ezbc commented 3 years ago

Thanks for considering the request and the initial ideas on implementation. I'd prefer the option of joining the FactoryBoy maintainers, though I'm not positive I'll have the expertise to implement the Pynamo ODM (good suggestion). I'll take a stab at a pull request and if it goes alright within a reasonable amount of time I'd be happy to support the project.

ezbc commented 3 years ago

For testing locally against DynamoDB, I'm considering adding a docker container provided by AWS. See the Docker tab in https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html. I suspect this would be the least overhead for new contributors, though I am of course new to this project and am not familiar with your ecosystem.

Would AWS Docker container work in your project?

The other options I see are

  1. different DynamoDB docker container maintained in localstack: https://github.com/localstack/localstack
  2. running the AWS-provided DynamoDB JAR on the host machine
ghost commented 2 years ago

@ezbc @rbarrois

Can I join the effort? I need a sound fixture replacement tool for my project (DynamoDB + PynamoDB), 'cuz those plain dictionary fixtures I have right now have started to go out of hands.

tasercake commented 2 years ago

Hey @ezbc, a Pynamo ODM would make things a lot easier for me too, so I'd love to pitch in where I can. Any chance you've made some progress on that PR?


For the time being, I'm seeing some success using a (very slightly) adapted version of @rbarrois's snippet:

Pynamo ODM ```python from typing import Type import factory from pynamodb.models import Model class PynamoModelFactory(factory.Factory): @classmethod def _build(cls, model_class: Type[BaseModel], *args, **kwargs): return model_class(*args, **kwargs) @classmethod def _create(cls, model_class: Type[BaseModel], *args, **kwargs): obj = cls._build(model_class, *args, **kwargs) obj.save() return obj ```
ezbc commented 2 years ago

@tasercake @g-kisenkov sorry, I started and changed focus so I don't have anything for this. @tasercake could you share the adapted version of the Pynamo factory snippet? I'm curious what it looks like.