├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1-issue.md │ └── config.yml ├── dependbot.yml └── workflows │ ├── publish.yml │ └── test-suite.yml ├── .gitignore ├── .pdbrc ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── Taskfile.yaml ├── compose.yml ├── docs ├── admin │ └── admin.md ├── connection.md ├── contenttypes │ ├── intro.md │ └── replace-elasticsearch.md ├── contributing.md ├── debugging.md ├── declarative-models.md ├── edgy-people.md ├── edgy.md ├── embedding.md ├── exceptions.md ├── extensions.md ├── fields │ ├── custom.md │ ├── index.md │ └── postgres.md ├── file-handling.md ├── index.md ├── inspectdb.md ├── managers.md ├── marshalls.md ├── migrations │ ├── discovery.md │ └── migrations.md ├── models.md ├── overrides │ ├── assets │ │ ├── bootstrap │ │ │ ├── css │ │ │ │ └── bootstrap.min.css │ │ │ └── js │ │ │ │ └── bootstrap.min.js │ │ ├── css │ │ │ ├── bs-theme-overrides.css │ │ │ └── esmerald.css │ │ ├── img │ │ │ └── favicon.ico │ │ └── js │ │ │ ├── esmerald.js │ │ │ └── startup-modern.js │ ├── home.html │ └── nav.html ├── pagination.md ├── permissions │ ├── intro.md │ └── passwords.md ├── queries │ ├── annotate.md │ ├── many-to-many.md │ ├── many-to-one.md │ ├── prefetch.md │ ├── queries.md │ ├── related-name.md │ └── secrets.md ├── reference-foreignkey.md ├── references │ ├── database.md │ ├── fields.md │ ├── foreignkey.md │ ├── index.md │ ├── manager.md │ ├── many-to-many.md │ ├── models.md │ ├── one-to-one.md │ ├── queryset.md │ ├── reference-foreign-key.md │ ├── reflect-model.md │ ├── registry.md │ ├── schemas.md │ └── signals.md ├── reflection │ ├── autoreflection.md │ └── reflection.md ├── registry.md ├── relationships.md ├── release-notes.md ├── settings.md ├── shell.md ├── signals.md ├── sponsorship.md ├── statics │ └── images │ │ ├── favicon.ico │ │ └── white.png ├── tenancy │ ├── contrib.md │ └── edgy.md ├── testing │ ├── index.md │ ├── model-factory.md │ └── test-client.md ├── tips-and-tricks.md └── transactions.md ├── docs_src ├── commands │ └── discover.py ├── connections │ ├── asgi.py │ ├── asynccontextmanager.py │ ├── contextmanager.py │ ├── contextmanager_with_loop.py │ ├── contextmanager_with_loop_and_cleanup.py │ ├── django.py │ ├── manual.py │ ├── manual_esmerald.py │ └── simple.py ├── contenttypes │ ├── basic.py │ ├── collision.py │ ├── contenttype_tags.py │ ├── customized_collision.py │ ├── customized_nocollision.py │ ├── m2m_with_contenttypes.py │ └── snapshotting.py ├── extensions │ ├── add_extension.py │ └── settings.py ├── fields │ └── files │ │ └── file_with_size.py ├── marshalls │ ├── exclude.py │ ├── fields.py │ ├── method_field.py │ ├── nutshell.py │ ├── save.py │ └── source.py ├── migrations │ ├── accounts_models.py │ ├── attaching.py │ ├── automigrations_library.py │ ├── automigrations_library_disabled.py │ ├── automigrations_main.py │ ├── fastapi.py │ ├── lru.py │ ├── migrations.py │ ├── model.py │ └── starlette.py ├── models │ ├── abstract │ │ ├── common.py │ │ └── simple.py │ ├── constraints.py │ ├── declarative │ │ ├── example.py │ │ └── fk_relationship.py │ ├── declaring_models.py │ ├── declaring_models_no_id.py │ ├── declaring_models_pk_no_id.py │ ├── default_model.py │ ├── indexes │ │ ├── complex_together.py │ │ ├── simple.py │ │ └── simple2.py │ ├── managers │ │ ├── custom.py │ │ ├── example.py │ │ ├── override.py │ │ ├── override_related.py │ │ └── simple.py │ ├── on_conflict.py │ ├── pk_no_default.py │ ├── pk_with_default.py │ ├── registry │ │ ├── inheritance_abstract.py │ │ ├── inheritance_no_repeat.py │ │ └── nutshell.py │ ├── tablename │ │ ├── model_diff_tn.py │ │ ├── model_no_tablename.py │ │ └── model_with_tablename.py │ └── unique_together │ │ ├── complex_combined.py │ │ ├── complex_independent.py │ │ ├── complex_mixed.py │ │ ├── complex_together.py │ │ ├── constraints │ │ ├── complex.py │ │ └── mixing.py │ │ ├── simple.py │ │ └── simple2.py ├── pagination │ ├── cursor_pagination.py │ ├── double_linked_list.py │ ├── esmerald_example.py │ ├── simple_pagination.py │ ├── using_attributes.py │ └── using_attributes_slow.py ├── permissions │ ├── advanced.py │ ├── example.py │ ├── passwordargon2id.py │ ├── passwordfield_basic.py │ ├── passwordfield_token.py │ ├── passwordpasslib.py │ ├── passwordretry.py │ ├── primary_key.py │ └── quickstart.py ├── prefetch │ ├── first │ │ ├── asserting.py │ │ ├── data.py │ │ ├── models.py │ │ └── prefetch.py │ └── second │ │ ├── data.py │ │ ├── models.py │ │ ├── prefetch.py │ │ └── prefetch_filtered.py ├── queries │ ├── annotate │ │ ├── child_annotate.py │ │ ├── child_annotate_embed.py │ │ ├── parent_annotate.py │ │ ├── strict_child_annotate.py │ │ └── subquery_annotate.py │ ├── clauses │ │ ├── and.py │ │ ├── and_m_filter.py │ │ ├── and_two.py │ │ ├── model.py │ │ ├── not.py │ │ ├── not_m_filter.py │ │ ├── not_two.py │ │ ├── or.py │ │ ├── or_m_filter.py │ │ ├── or_two.py │ │ └── style │ │ │ ├── and.py │ │ │ ├── and_m_filter.py │ │ │ ├── and_two.py │ │ │ ├── model.py │ │ │ ├── not.py │ │ │ ├── not_m_filter.py │ │ │ ├── not_two.py │ │ │ ├── or.py │ │ │ ├── or_m_filter.py │ │ │ └── or_two.py │ ├── manytomany │ │ ├── example.py │ │ ├── no_rel.py │ │ ├── no_rel_query_example.py │ │ └── query_example.py │ ├── manytoone │ │ └── example.py │ ├── model.py │ ├── related_name │ │ ├── data.py │ │ ├── example.py │ │ ├── models.py │ │ ├── new_data.py │ │ └── new_models.py │ └── secrets │ │ └── model.py ├── quickstart │ ├── esmerald.py │ └── example1.py ├── reffk │ ├── apis │ │ ├── api_call.py │ │ ├── api_call_errors.py │ │ └── complex_example.py │ ├── complex_example.py │ ├── example1.py │ ├── model_ref │ │ ├── how_to_declare.py │ │ ├── model_ref.py │ │ └── model_ref2.py │ ├── nutshell.py │ ├── positional_example.py │ └── references.py ├── reflection │ ├── autoreflection │ │ ├── datadriven.py │ │ ├── datadriven_source.py │ │ └── legacy.py │ ├── model.py │ ├── reflect.py │ └── reflect │ │ ├── model.py │ │ └── reflect.py ├── registry │ ├── create_schema.py │ ├── custom_registry.py │ ├── default_schema.py │ ├── drop_schema.py │ ├── extra │ │ ├── create.py │ │ └── declaration.py │ ├── model.py │ └── multiple.py ├── relationships │ ├── embed_parent_with_embedded.py │ ├── model.py │ ├── multiple.py │ └── onetoone.py ├── settings │ └── custom_settings.py ├── shared │ ├── extra.md │ └── notes.md ├── signals │ ├── custom.py │ ├── disconnect.py │ ├── logic.py │ ├── receiver │ │ ├── disconnect.py │ │ ├── model.py │ │ ├── multiple.py │ │ ├── multiple_receivers.py │ │ ├── post_multiple.py │ │ └── post_save.py │ ├── register.py │ └── rewire.py ├── tenancy │ ├── contrib │ │ ├── domain_mixin.py │ │ ├── example │ │ │ ├── api.py │ │ │ ├── app.py │ │ │ ├── middleware.py │ │ │ ├── mock_data.py │ │ │ ├── models.py │ │ │ ├── queries.py │ │ │ └── settings.py │ │ ├── tenant_mixin.py │ │ ├── tenant_model.py │ │ └── tenant_user_mixin.py │ ├── example │ │ ├── api.py │ │ ├── data.py │ │ ├── middleware.py │ │ ├── models.py │ │ └── query.py │ └── using │ │ └── schemas.py ├── testing │ ├── factory │ │ ├── factory_basic.py │ │ ├── factory_build.py │ │ ├── factory_exclude.py │ │ ├── factory_field_overwrite.py │ │ ├── factory_fields_exclude.py │ │ ├── factory_fields_exclude_autoincrement.py │ │ ├── factory_mapping.py │ │ ├── factory_mapping2.py │ │ ├── factory_parametrize.py │ │ ├── factory_save.py │ │ ├── factory_to_field.py │ │ ├── factory_to_fields.py │ │ ├── sequences.py │ │ ├── sequences_even.py │ │ ├── sequences_odd.py │ │ └── sequences_subfactory.py │ └── testclient │ │ └── tests.py ├── tips │ ├── connection.py │ ├── lru.py │ ├── migrations.py │ ├── models.py │ ├── sandwich_main.py │ ├── sandwich_models.py │ └── settings.py └── transactions │ ├── context_manager.py │ ├── context_manager2.py │ ├── context_manager_direct.py │ ├── decorator.py │ └── models.py ├── edgy ├── __init__.py ├── __main__.py ├── _monkay.py ├── cli │ ├── __init__.py │ ├── base.py │ ├── cli.py │ ├── constants.py │ ├── decorators.py │ ├── exceptions.py │ ├── operations │ │ ├── __init__.py │ │ ├── admin_serve.py │ │ ├── branches.py │ │ ├── check.py │ │ ├── current.py │ │ ├── downgrade.py │ │ ├── edit.py │ │ ├── heads.py │ │ ├── history.py │ │ ├── init.py │ │ ├── inspectdb.py │ │ ├── list_templates.py │ │ ├── makemigrations.py │ │ ├── merge.py │ │ ├── migrate.py │ │ ├── revision.py │ │ ├── shell │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── enums.py │ │ │ ├── ipython.py │ │ │ ├── ptpython.py │ │ │ └── utils.py │ │ ├── show.py │ │ └── stamp.py │ └── templates │ │ ├── default │ │ ├── README │ │ ├── alembic.ini.mako │ │ ├── env.py │ │ └── script.py.mako │ │ ├── plain │ │ ├── README │ │ ├── alembic.ini.mako │ │ ├── env.py │ │ └── script.py.mako │ │ ├── sequencial │ │ ├── README │ │ ├── alembic.ini.mako │ │ ├── env.py │ │ ├── generator.py │ │ └── script.py.mako │ │ └── url │ │ ├── README │ │ ├── alembic.ini.mako │ │ ├── env.py │ │ └── script.py.mako ├── conf │ ├── __init__.py │ ├── enums.py │ ├── global_settings.py │ └── module_import.py ├── contrib │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── application.py │ │ ├── config.py │ │ ├── mixins.py │ │ ├── templates │ │ │ ├── 404.html │ │ │ └── admin │ │ │ │ ├── base.html │ │ │ │ ├── dashboard.html │ │ │ │ ├── model_detail.html │ │ │ │ ├── model_object_create.html │ │ │ │ ├── model_object_detail.html │ │ │ │ ├── model_object_edit.html │ │ │ │ └── models.html │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── messages.py │ │ │ └── models.py │ │ └── views.py │ ├── autoreflection │ │ ├── __init__.py │ │ ├── metaclasses.py │ │ └── models.py │ ├── contenttypes │ │ ├── __init__.py │ │ ├── fields.py │ │ ├── metaclasses.py │ │ └── models.py │ ├── multi_tenancy │ │ ├── __init__.py │ │ ├── base.py │ │ ├── metaclasses.py │ │ ├── models.py │ │ ├── registry.py │ │ ├── settings.py │ │ └── utils.py │ ├── pagination │ │ ├── __init__.py │ │ ├── base.py │ │ └── cursor.py │ └── permissions │ │ ├── __init__.py │ │ ├── managers.py │ │ └── models.py ├── core │ ├── __init__.py │ ├── connection │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── database.py │ │ ├── registry.py │ │ └── schemas.py │ ├── datastructures.py │ ├── db │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── context_vars.py │ │ ├── datastructures.py │ │ ├── fields │ │ │ ├── __init__.py │ │ │ ├── _internal.py │ │ │ ├── base.py │ │ │ ├── composite_field.py │ │ │ ├── computed_field.py │ │ │ ├── core.py │ │ │ ├── exclude_field.py │ │ │ ├── factories.py │ │ │ ├── file_field.py │ │ │ ├── foreign_keys.py │ │ │ ├── image_field.py │ │ │ ├── many_to_many.py │ │ │ ├── mixins.py │ │ │ ├── one_to_one_keys.py │ │ │ ├── place_holder_field.py │ │ │ ├── postgres.py │ │ │ ├── ref_foreign_key.py │ │ │ └── types.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── managers.py │ │ │ ├── metaclasses.py │ │ │ ├── mixins │ │ │ │ ├── __init__.py │ │ │ │ ├── db.py │ │ │ │ ├── generics.py │ │ │ │ ├── reflection.py │ │ │ │ └── row.py │ │ │ ├── model.py │ │ │ ├── model_reference.py │ │ │ ├── types.py │ │ │ └── utils.py │ │ ├── querysets │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── clauses.py │ │ │ ├── mixins.py │ │ │ ├── prefetch.py │ │ │ └── types.py │ │ └── relationships │ │ │ ├── __init__.py │ │ │ ├── related_field.py │ │ │ ├── relation.py │ │ │ └── utils.py │ ├── events.py │ ├── files │ │ ├── __init__.py │ │ ├── base.py │ │ ├── locks.py │ │ ├── move.py │ │ └── storage │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── filesystem.py │ │ │ └── handler.py │ ├── marshalls │ │ ├── __init__.py │ │ ├── base.py │ │ ├── config.py │ │ ├── fields.py │ │ └── metaclasses.py │ ├── signals.py │ ├── tenancy │ │ ├── __init__.py │ │ └── utils.py │ ├── terminal │ │ ├── __init__.py │ │ ├── base.py │ │ ├── print.py │ │ └── terminal.py │ └── utils │ │ ├── __init__.py │ │ ├── db.py │ │ ├── functional.py │ │ ├── models.py │ │ └── sync.py ├── exceptions.py ├── protocols │ ├── __init__.py │ ├── many_relationship.py │ └── transaction_call.py ├── py.typed ├── testclient.py ├── testing │ ├── __init__.py │ ├── client.py │ ├── exceptions.py │ └── factory │ │ ├── __init__.py │ │ ├── base.py │ │ ├── context_vars.py │ │ ├── fields.py │ │ ├── mappings.py │ │ ├── metaclasses.py │ │ ├── subfactory.py │ │ ├── types.py │ │ └── utils.py ├── types.py └── utils │ ├── __init__.py │ ├── compat.py │ ├── hashing.py │ ├── inspect.py │ └── path.py ├── mkdocs.yml ├── pyproject.toml ├── scripts ├── clean └── install └── tests ├── __init__.py ├── clauses ├── __init__.py ├── test_and_clauses.py ├── test_not_clauses.py └── test_or_clauses.py ├── cli ├── __init__.py ├── conftest.py ├── custom_multidb │ ├── README │ ├── alembic.ini.mako │ ├── env.py │ └── script.py.mako ├── custom_multidb_copied_registry │ ├── README │ ├── alembic.ini.mako │ ├── env.py │ └── script.py.mako ├── custom_singledb │ ├── README │ ├── alembic.ini.mako │ ├── env.py │ └── script.py.mako ├── main.py ├── main_multidb.py ├── main_multidb_nonidentifier.py ├── main_server_defaults.py ├── test_alembic.py ├── test_inspectdb.py ├── test_lazy_evaluation_settings.py ├── test_multidb_templates.py ├── test_nullable_fields.py ├── test_nullable_generic_fields.py ├── test_server_default_fields.py ├── test_templates.py └── utils.py ├── conftest.py ├── contrib ├── __init__.py ├── admin │ └── test_admin.py ├── autoreflection │ ├── test_reflecting_models.py │ └── test_reflecting_models_schema.py ├── contenttypes │ ├── test_contenttypes.py │ ├── test_contenttypes_custom.py │ ├── test_contenttypes_custom_non_abstract.py │ ├── test_contenttypes_different_registry.py │ ├── test_contenttypes_iterate.py │ ├── test_contenttypes_multi_tenancy.py │ └── test_contenttypes_multi_tenancy_custom.py ├── multi_tenancy │ ├── test_migrate.py │ ├── test_mt_models.py │ ├── test_tenant_models_using.py │ ├── test_tenant_models_using_operations.py │ └── test_tenant_models_working.py ├── pagination │ ├── test_esmerald_integration_cursor.py │ ├── test_pagination.py │ └── test_pagination_reversed.py └── permissions │ ├── test_advanced_permissions.py │ ├── test_group_permissions.py │ └── test_simple_permissions.py ├── exclude_secrets ├── test_exclude.py ├── test_exclude_nested.py ├── test_exclude_repeated.py ├── test_secrets.py └── test_secrets_fk.py ├── extensions └── test_extensions.py ├── factory ├── __init__.py ├── generation │ ├── __init__.py │ └── test_basic_factory.py ├── test_factory.py ├── test_factory_errors.py ├── test_mappings.py ├── test_parameters.py ├── test_save.py └── test_to_field.py ├── fields ├── __init__.py ├── test_composite_fields.py ├── test_composite_fields_column_name.py ├── test_date_field.py ├── test_datetime.py ├── test_datetime_field.py ├── test_decimal_field.py ├── test_defaults.py ├── test_duration_field.py ├── test_field_validators.py ├── test_fields.py ├── test_file_fields.py ├── test_foreignkeys.py ├── test_image_fields.py ├── test_indb_updates.py ├── test_inheritance.py ├── test_ip_field.py ├── test_multi_column_fields.py ├── test_password_field.py ├── test_pg_array_field.py ├── test_server_defaults.py └── test_url_field.py ├── foreign_keys ├── m2m_string │ ├── __init__.py │ ├── test_many_to_many.py │ ├── test_many_to_many_field.py │ ├── test_many_to_many_field_related_name.py │ ├── test_many_to_many_no_related_name.py │ └── test_many_to_many_related_name.py ├── m2m_string_old │ ├── __init__.py │ ├── test_many_to_many.py │ ├── test_many_to_many_field.py │ ├── test_many_to_many_field_related_name.py │ ├── test_many_to_many_no_related_name.py │ └── test_many_to_many_related_name.py ├── test_cross_registry_and_db.py ├── test_fk_related_name.py ├── test_foreignkey.py ├── test_foreignkey_deep_embed_parent.py ├── test_foreignkey_embed_parent.py ├── test_foreignkey_special.py ├── test_foreignkey_unique_and_no_constraint.py ├── test_many_to_many.py ├── test_many_to_many_field.py ├── test_many_to_many_field_new_naming_multiple.py ├── test_many_to_many_field_old.py ├── test_many_to_many_field_related_name.py ├── test_many_to_many_field_related_name_old.py ├── test_many_to_many_field_special.py ├── test_many_to_many_field_special_auto.py ├── test_many_to_many_old.py ├── test_many_to_many_related_name.py ├── test_many_to_many_related_name_embedded.py ├── test_many_to_many_related_name_old.py ├── test_many_to_many_unique.py └── test_ref_foreignkey.py ├── images ├── mini_image.jpg └── mini_image.png ├── indexes ├── test_indexes.py ├── test_indexes_clauses.py ├── test_indexes_errors.py └── test_indexes_inherit_abstract.py ├── integration ├── test_esmerald_create_and_return_model.py ├── test_esmerald_create_and_return_model_list_fk.py ├── test_esmerald_fk_reference_middleware.py ├── test_esmerald_models_validation_error.py ├── test_esmerald_tenant.py └── test_esmerald_tenant_user.py ├── managers ├── test_manager_error.py ├── test_manager_simple.py ├── test_managers.py ├── test_managers_abstract.py ├── test_managers_inherited.py ├── test_managers_related.py └── test_queryset.py ├── marshalls ├── __init__.py ├── save │ ├── __init__.py │ ├── test_marshall_all.py │ ├── test_marshall_all_with_custom.py │ ├── test_marshall_exclude.py │ ├── test_marshall_import_string.py │ ├── test_marshall_method_field.py │ ├── test_marshall_source.py │ ├── test_marshall_source_from_property.py │ └── test_simple_marshall.py ├── test_marshall_all.py ├── test_marshall_all_with_custom.py ├── test_marshall_config_errors.py ├── test_marshall_context.py ├── test_marshall_exclude.py ├── test_marshall_import_string.py ├── test_marshall_method_field.py ├── test_marshall_source.py ├── test_marshall_source_from_property.py ├── test_simple_marshall.py ├── test_simple_marshall_different_explicit_pk.py └── test_simple_marshall_different_id.py ├── metaclass ├── test_lazyness.py ├── test_meta.py └── test_meta_errors.py ├── models ├── run_sync │ ├── __init__.py │ ├── test_bulk_create.py │ ├── test_bulk_update.py │ ├── test_model_abstract.py │ ├── test_model_class.py │ ├── test_model_count.py │ ├── test_model_defer.py │ ├── test_model_distinct.py │ ├── test_model_exists.py │ ├── test_model_first.py │ ├── test_model_get_or_create.py │ ├── test_model_get_or_none.py │ ├── test_model_group_by.py │ ├── test_model_inheritance.py │ ├── test_model_last.py │ ├── test_model_limit.py │ ├── test_model_multiple_inheritance.py │ ├── test_model_offset.py │ ├── test_model_only.py │ ├── test_model_order_by.py │ ├── test_model_primary_key.py │ ├── test_model_queryset_delete.py │ ├── test_model_queryset_update.py │ ├── test_model_registry.py │ ├── test_model_search.py │ ├── test_model_sqlalchemy.py │ ├── test_model_sync.py │ ├── test_model_update_or_create.py │ ├── test_model_values.py │ ├── test_model_values_list.py │ ├── test_models_filter.py │ └── test_save.py ├── test_bulk_create.py ├── test_bulk_get_or_create.py ├── test_bulk_update.py ├── test_embeddables.py ├── test_increment_onsave.py ├── test_inner_select.py ├── test_model_abstract.py ├── test_model_class.py ├── test_model_class_mixed.py ├── test_model_copying.py ├── test_model_count.py ├── test_model_defer.py ├── test_model_distinct.py ├── test_model_exists.py ├── test_model_first.py ├── test_model_get_or_create.py ├── test_model_get_or_none.py ├── test_model_group_by.py ├── test_model_inheritance.py ├── test_model_last.py ├── test_model_limit.py ├── test_model_multiple_inheritance.py ├── test_model_multiple_primary_keys.py ├── test_model_offset.py ├── test_model_only.py ├── test_model_order_by.py ├── test_model_primary_key.py ├── test_model_proxy.py ├── test_model_queryset_delete.py ├── test_model_queryset_update.py ├── test_model_registry.py ├── test_model_search.py ├── test_model_sqlalchemy.py ├── test_model_update_or_create.py ├── test_model_values.py ├── test_model_values_list.py ├── test_models_extra_and_reference.py ├── test_models_extra_and_reference_embed.py ├── test_models_filter.py ├── test_nested_dump.py ├── test_reverse.py ├── test_save.py ├── test_select_related_mul.py ├── test_select_related_nested.py └── test_select_related_single.py ├── prefetch ├── test_prefetch_error.py ├── test_prefetch_multiple.py ├── test_prefetch_multiple_iterate.py ├── test_prefetch_object.py ├── test_prefetch_related_nested.py └── test_prefetch_related_with_select_related.py ├── pydantic_tests ├── __init__.py └── test_fields.py ├── querysets └── test_queryset_class.py ├── reflection ├── test_table_reflection.py ├── test_table_reflection_schemes.py └── test_table_reflection_special_fields.py ├── registry ├── test_different_registry_default.py ├── test_registries.py ├── test_registry.py ├── test_registry_copying.py └── test_registry_run_sync.py ├── settings ├── __init__.py ├── default.py ├── disabled_auto_server_defaults.py ├── multidb.py └── multidb_nonidentifier.py ├── signals ├── test_deletion_signals.py └── test_signals.py ├── tenancy ├── test_activate_tenant.py ├── test_activate_tenant_precedent.py ├── test_load.py ├── test_mt_bulk_create.py ├── test_mt_bulk_create_different_db.py ├── test_normal_tenancy.py ├── test_raise_assertation_error.py ├── test_select_related.py ├── test_select_related_multiple.py ├── test_select_related_two.py └── test_tenancy_queries.py ├── test_automigrations.py ├── test_columns.py ├── test_constraints.py ├── test_database_rollback.py ├── test_database_url.py ├── test_db_connected_warnings.py ├── test_declarative_models.py ├── test_lazy_imports.py ├── test_migrate.py ├── test_transactions.py └── uniques ├── test_unique.py ├── test_unique_constraint.py ├── test_unique_constraint_inherited.py ├── test_unique_together.py └── test_unique_together_inherited.py /.editorconfig: -------------------------------------------------------------------------------- 1 | ; More information at http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = false 11 | 12 | # See Google Shell Style Guide 13 | # https://google.github.io/styleguide/shell.xml 14 | [*.sh] 15 | indent_size = 2 # shfmt: like -i 2 16 | insert_final_newline = true 17 | switch_case_indent = true # shfmt: like -ci 18 | 19 | [*.py] 20 | insert_final_newline = true 21 | indent_size = 4 22 | max_line_length = 120 23 | 24 | [*.md] 25 | trim_trailing_whitespace = false 26 | indent_size = 4 27 | 28 | [*.yml] 29 | indent_size = 2 30 | 31 | [Makefile] 32 | indent_style = tab 33 | 34 | [*.js] 35 | indent_size = 4 36 | insert_final_newline = true 37 | 38 | [*.ts] 39 | insert_final_newline = true 40 | 41 | [*.scss] 42 | insert_final_newline = true 43 | 44 | [*.json] 45 | indent_size = 2 46 | 47 | [*.html] 48 | insert_final_newline = true 49 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tarsil 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Please only raise an issue if you've been advised to do so after discussion. Much appreciated! 🙏 4 | --- 5 | 6 | The starting point for issues should usually be a discussion... 7 | 8 | https://github.com/dymmond/edgy/discussions 9 | 10 | Potential bugs may be raised as a "Potential Issue" discussion. The feature requests may be raised as an 11 | "Ideas" discussion. 12 | 13 | We can then decide if the discussion needs to be escalated into an "Issue" or not. 14 | 15 | This will make sure that everything is organised properly. 16 | --- 17 | 18 | **Edgy version**: 19 | **Python version**: 20 | **OS**: 21 | **Platform**: 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Discussions 5 | url: https://github.com/dymmond/edgy/discussions 6 | about: > 7 | The "Discussions" forum is where you want to start. 8 | -------------------------------------------------------------------------------- /.github/dependbot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: ⬆ 9 | - package-ecosystem: "pip" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | commit-message: 14 | prefix: ⬆ 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | 4 | on: 5 | push: 6 | tags: 7 | - '[0-9]*' 8 | 9 | jobs: 10 | publish: 11 | name: "Publish release" 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | - uses: "actions/checkout@v4" 16 | - uses: "actions/setup-python@v5" 17 | - name: Install build dependencies 18 | if: steps.cache.outputs.cache-hit != 'true' 19 | run: pip install hatch 20 | - name: "Build package" 21 | run: "hatch build" 22 | - name: "Publish to PyPI" 23 | run: "hatch publish -n" 24 | env: 25 | HATCH_INDEX_USER: __token__ 26 | HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }} 27 | - name: "Deploy docs" 28 | run: | 29 | curl -X POST '${{ secrets.DEPLOY_DOCS }}' 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folders 2 | *.egg-info/ 3 | .hypothesis/ 4 | .idea/ 5 | .mypy_cache/ 6 | .pytest_cache/ 7 | .scannerwork/ 8 | .tox/ 9 | .venv/ 10 | venv/ 11 | .vscode/ 12 | __pycache__/ 13 | virtualenv/ 14 | build/ 15 | dist/ 16 | node_modules/ 17 | results/ 18 | site/ 19 | target/ 20 | tests/cli/migrations 21 | tests/cli/migrations2 22 | media/ 23 | test_media/ 24 | 25 | # files 26 | **/*.so 27 | *.sqlite 28 | *.iml 29 | .DS_Store 30 | .coverage 31 | .coverage.* 32 | .python-version 33 | coverage.* 34 | docker-compose.override.yml 35 | compose.override.yml 36 | *.zip 37 | -------------------------------------------------------------------------------- /.pdbrc: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | alias kkk os.system('kill -9 %d' % os.getpid()) 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information. 2 | # See https://pre-commit.com/hooks.html for more hooks. 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-added-large-files 8 | - id: check-toml 9 | - id: debug-statements 10 | - id: check-yaml 11 | args: 12 | - --unsafe 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | - repo: https://github.com/charliermarsh/ruff-pre-commit 16 | rev: v0.11.5 17 | hooks: 18 | - id: ruff 19 | args: ["--fix"] 20 | 21 | ci: 22 | autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks 23 | autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate 24 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | restart: always 4 | image: postgres:16 5 | environment: 6 | POSTGRES_HOST_AUTH_METHOD: trust 7 | POSTGRES_USER: "postgres" 8 | POSTGRES_PASSWORD: "passwsss*1348394#" 9 | POSTGRES_DB: "edgy" 10 | expose: 11 | - "5432" 12 | # - ./create_db.sh:/docker-entrypoint-initdb.d/create_db.sh 13 | command: >- 14 | --jit=false 15 | ports: 16 | - "127.0.0.1:5432:5432" 17 | 18 | edgy_alt: 19 | restart: always 20 | image: postgres:16 21 | environment: 22 | POSTGRES_HOST_AUTH_METHOD: trust 23 | POSTGRES_USER: "postgres" 24 | POSTGRES_PASSWORD: "postgres" 25 | POSTGRES_DB: "edgy_alt" 26 | command: >- 27 | --jit=false 28 | ports: 29 | - "127.0.0.1:5433:5432" 30 | -------------------------------------------------------------------------------- /docs/admin/admin.md: -------------------------------------------------------------------------------- 1 | # Admin 2 | 3 | ## What is the admin feature? 4 | 5 | This is an **experimental** Web-GUI to expose the database for the webbrowser. 6 | Admins can use the GUI to fix some problems. 7 | 8 | !!! Warning 9 | Currently you are responsible for securing the powerful admin. Never expose to untrusted parties. 10 | 11 | ## Using Admin from cli 12 | 13 | Use something like: 14 | 15 | `edgy --app tests.cli.main admin_serve --create-all` 16 | 17 | !!! Warning 18 | Never expose to 0.0.0.0. We have no security as well as user permission management yet. So it is highly recommended 19 | to protect the admin interface by including it in your project. 20 | 21 | 22 | ## Embedding Admin 23 | 24 | See edgy/cli/operations/admin_serve.py to get an idea. 25 | 26 | 27 | ## Excluding models 28 | 29 | Just set in Meta, the flag `in_admin` to `False`. This flag is inherited. 30 | -------------------------------------------------------------------------------- /docs/edgy-people.md: -------------------------------------------------------------------------------- 1 | # Edgy Contributors 2 | 3 | 4 | Edgy wouldn't be possible in this way without these amazing people. 5 | 6 | - [@kokoserver](https://github.com/Kokoserver) - Provided a lot of valuable bug reports and PRs. Especially related to migrations. 7 | 8 | - [@devkral](https://github.com/devkral) - Revamped an reworked Edgy to make it one of the most powerful ORMs out there. His extensive experience with databases are invaluable to the success of Edgy. Some features he provided are FileFields, Registry hooks, copying models and a lot more. 9 | -------------------------------------------------------------------------------- /docs/exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | All **Edgy** custom exceptions derive from the base `EdgyException`. 4 | 5 | ## ObjectNotFound 6 | 7 | Raised when querying a model instance and it does not exist. 8 | 9 | ```python 10 | from edgy.exceptions import ObjectNotFound 11 | ``` 12 | 13 | Or simply: 14 | 15 | ```python 16 | from edgy import ObjectNotFound 17 | ``` 18 | 19 | ## MultipleObjectsReturned 20 | 21 | Raised when querying a model and returns multiple results for the given query result. 22 | 23 | ```python 24 | from edgy.exceptions import MultipleObjectsReturned 25 | ``` 26 | 27 | Or simply: 28 | 29 | ```python 30 | from edgy import MultipleObjectsReturned 31 | ``` 32 | 33 | ## ValidationError 34 | 35 | Raised when a validation error is thrown. 36 | 37 | ```python 38 | from edgy.exceptions import ValidationError 39 | ``` 40 | 41 | Or simply: 42 | 43 | ```python 44 | from edgy import ValidationError 45 | ``` 46 | 47 | ## ImproperlyConfigured 48 | 49 | Raised when misconfiguration in the models and metaclass is passed. 50 | 51 | ```python 52 | from edgy.exceptions import ImproperlyConfigured 53 | ``` 54 | 55 | Or simply: 56 | 57 | ```python 58 | from edgy import ImproperlyConfigured 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | title: Edgy | 🔥 The perfect ORM to work with complex databases 🔥. 4 | --- 5 | 6 | Welcome to Edgy. 7 | -------------------------------------------------------------------------------- /docs/overrides/assets/css/esmerald.css: -------------------------------------------------------------------------------- 1 | .bs-icon.bs-icon-secondary { 2 | color: var(--bs-primary); 3 | background: var(--bs-primary); 4 | } 5 | 6 | .underline:after { 7 | content: ""; 8 | position: absolute; 9 | bottom: -2px; 10 | left: 0; 11 | width: 100%; 12 | height: 8px; 13 | border-radius: 5px; 14 | background: var(--bs-primary); 15 | z-index: -1; 16 | } 17 | -------------------------------------------------------------------------------- /docs/overrides/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/docs/overrides/assets/img/favicon.ico -------------------------------------------------------------------------------- /docs/overrides/assets/js/esmerald.js: -------------------------------------------------------------------------------- 1 | hljs.highlightAll(); 2 | -------------------------------------------------------------------------------- /docs/overrides/assets/js/startup-modern.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; // Start of use strict 3 | 4 | var mainNav = document.querySelector('#mainNav'); 5 | 6 | if (mainNav) { 7 | 8 | // Collapse Navbar 9 | var collapseNavbar = function() { 10 | 11 | var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop; 12 | 13 | if (scrollTop > 100) { 14 | mainNav.classList.add("navbar-shrink"); 15 | } else { 16 | mainNav.classList.remove("navbar-shrink"); 17 | } 18 | }; 19 | // Collapse now if page is not at top 20 | collapseNavbar(); 21 | // Collapse the navbar when page is scrolled 22 | document.addEventListener("scroll", collapseNavbar); 23 | } 24 | 25 | })(); // End of use strict 26 | -------------------------------------------------------------------------------- /docs/overrides/nav.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block outdated %} You're not viewing the latest 2 | version. 3 | 4 | Click here to go to latest. 5 | 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /docs/references/database.md: -------------------------------------------------------------------------------- 1 | # **`Database`** class 2 | 3 | 4 | ::: edgy.Database 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | - "!^__aenter__" 11 | - "!^__aexit__" 12 | - "!^SUPPORTED_BACKENDS" 13 | - "!^DIRECT_URL_SCHEME" 14 | - "!^MANDATORY_FIELDS" 15 | -------------------------------------------------------------------------------- /docs/references/fields.md: -------------------------------------------------------------------------------- 1 | # **`BaseField`** class 2 | 3 | 4 | ::: edgy.core.db.fields.base.BaseField 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | -------------------------------------------------------------------------------- /docs/references/foreignkey.md: -------------------------------------------------------------------------------- 1 | # **`ForeignKey`** class 2 | 3 | 4 | ::: edgy.fields.ForeignKey 5 | options: 6 | filters: 7 | - "!^field_type" 8 | - "!^model_config" 9 | - "!^__slots__" 10 | - "!^__getattr__" 11 | -------------------------------------------------------------------------------- /docs/references/index.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | Here lies all the useful API references for the classes, functions and attributes of all 4 | parts you can use in your application development. 5 | 6 | If you want to start with **Edgy** you should always [start here](https://edgy.dymmond.com/edgy). 7 | -------------------------------------------------------------------------------- /docs/references/manager.md: -------------------------------------------------------------------------------- 1 | # **`Manager`** class 2 | 3 | 4 | ::: edgy.Manager 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | -------------------------------------------------------------------------------- /docs/references/many-to-many.md: -------------------------------------------------------------------------------- 1 | # **`ManyToMany`** class 2 | 3 | 4 | ::: edgy.fields.ManyToManyField 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | - "!^field_type" 11 | -------------------------------------------------------------------------------- /docs/references/models.md: -------------------------------------------------------------------------------- 1 | # **`Model`** class 2 | 3 | 4 | ::: edgy.Model 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__dict__" 9 | - "!^__repr__" 10 | - "!^__str__" 11 | -------------------------------------------------------------------------------- /docs/references/one-to-one.md: -------------------------------------------------------------------------------- 1 | # **`OneToOne`** class 2 | 3 | 4 | ::: edgy.fields.OneToOne 5 | options: 6 | filters: 7 | - "!^field_type" 8 | - "!^model_config" 9 | - "!^__slots__" 10 | - "!^__getattr__" 11 | - "!^__new__" 12 | -------------------------------------------------------------------------------- /docs/references/queryset.md: -------------------------------------------------------------------------------- 1 | # **`QuerySet`** class 2 | 3 | 4 | ::: edgy.QuerySet 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__await__" 10 | - "!^__class_getitem__" 11 | - "!^__get__" 12 | - "!^ESCAPE_CHARACTERS" 13 | - "!^pknames" 14 | -------------------------------------------------------------------------------- /docs/references/reference-foreign-key.md: -------------------------------------------------------------------------------- 1 | # **`RefForeignKey`** class 2 | 3 | 4 | ::: edgy.fields.RefForeignKey 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__new__" 9 | -------------------------------------------------------------------------------- /docs/references/reflect-model.md: -------------------------------------------------------------------------------- 1 | # **`ReflectModel`** class 2 | 3 | 4 | ::: edgy.ReflectModel 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__dict__" 9 | - "!^__repr__" 10 | - "!^__str__" 11 | -------------------------------------------------------------------------------- /docs/references/registry.md: -------------------------------------------------------------------------------- 1 | # **`Registry`** class 2 | 3 | 4 | ::: edgy.Registry 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | -------------------------------------------------------------------------------- /docs/references/schemas.md: -------------------------------------------------------------------------------- 1 | # **`Schema`** class 2 | 3 | 4 | ::: edgy.core.connection.schemas.Schema 5 | options: 6 | filters: 7 | - "!^model_config" 8 | - "!^__slots__" 9 | - "!^__getattr__" 10 | - "!^__aenter__" 11 | - "!^__aexit__" 12 | - "!^SUPPORTED_BACKENDS" 13 | - "!^DIRECT_URL_SCHEME" 14 | - "!^MANDATORY_FIELDS" 15 | -------------------------------------------------------------------------------- /docs/references/signals.md: -------------------------------------------------------------------------------- 1 | # **`Signal`** class (third-party) 2 | 3 | 4 | ::: blinker.Signal 5 | 6 | 7 | ## Further documentation 8 | 9 | Blinker is a third-party library, more documentation is under: 10 | 11 | [Blinker documentation](https://blinker.readthedocs.io/en/stable/) 12 | 13 | Note: in earlier versions edgy implemented signals itself, 14 | so there are maybe some wrong examples lingering around. 15 | -------------------------------------------------------------------------------- /docs/statics/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/docs/statics/images/favicon.ico -------------------------------------------------------------------------------- /docs/statics/images/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/docs/statics/images/white.png -------------------------------------------------------------------------------- /docs/testing/index.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Edgy provides two testing facilities: 4 | 5 | - A [Testclient](test-client.md), which has some testing-only methods and parameters. 6 | - A faker based ModelFactory [ModelFactory](./model-factory.md) 7 | -------------------------------------------------------------------------------- /docs_src/commands/discover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | from esmerald import Esmerald, Include 7 | from my_project.utils import get_db_connection 8 | 9 | 10 | def build_path(): 11 | """ 12 | Builds the path of the project and project root. 13 | """ 14 | Path(__file__).resolve().parent.parent 15 | SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) 16 | 17 | if SITE_ROOT not in sys.path: 18 | sys.path.append(SITE_ROOT) 19 | sys.path.append(os.path.join(SITE_ROOT, "apps")) 20 | 21 | 22 | def get_application(): 23 | """ 24 | Encapsulating in methods can be useful for controlling the import order but is optional. 25 | """ 26 | from edgy import Instance, monkay 27 | 28 | build_path() 29 | registry = get_db_connection() 30 | 31 | app = registry.asgi( 32 | Esmerald( 33 | routes=[Include(namespace="my_project.urls")], 34 | ) 35 | ) 36 | 37 | monkay.set_instance(Instance(registry=registry, app=app)) 38 | return app 39 | 40 | 41 | app = get_application() 42 | -------------------------------------------------------------------------------- /docs_src/connections/asgi.py: -------------------------------------------------------------------------------- 1 | from esmerald import Esmerald 2 | 3 | from edgy import Registry, Instance, monkay 4 | 5 | models = Registry(database="sqlite:///db.sqlite", echo=True) 6 | 7 | 8 | app = models.asgi( 9 | Esmerald( 10 | routes=[...], 11 | ) 12 | ) 13 | 14 | # load settings 15 | monkay.evaluate_settings(ignore_import_errors=False) 16 | # monkey-patch app so you can use edgy shell 17 | monkay.set_instance(Instance(registry=registry, app=app)) 18 | -------------------------------------------------------------------------------- /docs_src/connections/asynccontextmanager.py: -------------------------------------------------------------------------------- 1 | from edgy import Registry, Instance, monkay 2 | 3 | models = Registry(database="sqlite:///db.sqlite", echo=True) 4 | 5 | 6 | async def main(): 7 | # load settings 8 | monkay.evaluate_settings(ignore_import_errors=False) 9 | # monkey-patch so you can use edgy shell 10 | monkay.set_instance(Instance(registry=registry)) 11 | async with models: 12 | ... 13 | -------------------------------------------------------------------------------- /docs_src/connections/contextmanager.py: -------------------------------------------------------------------------------- 1 | from edgy import Registry, Instance, monkay 2 | 3 | models = Registry(database="sqlite:///db.sqlite", echo=True) 4 | 5 | 6 | # load settings 7 | monkay.evaluate_settings(ignore_import_errors=False) 8 | # monkey-patch app so you can use edgy shell 9 | monkay.set_instance(Instance(registry=registry)) 10 | 11 | 12 | def main(): 13 | with models.with_async_env(): 14 | edgy.run_sync(User.query.all()) 15 | -------------------------------------------------------------------------------- /docs_src/connections/contextmanager_with_loop.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from contextvars import ContextVar 3 | from edgy import Registry, Instance, monkay, run_sync 4 | 5 | models = Registry(database="sqlite:///db.sqlite", echo=True) 6 | 7 | 8 | # multithreading safe 9 | event_loop = ContextVar("event_loop", default=None) 10 | 11 | 12 | def handle_request(): 13 | loop = event_loop.get() 14 | if loop is None: 15 | # eventloops die by default with the thread 16 | loop = asyncio.new_event_loop() 17 | event_loop.set(loop) 18 | with models.with_loop(loop): 19 | edgy.run_sync(User.query.all()) 20 | 21 | 22 | def get_application(): 23 | app = ... 24 | # load settings 25 | monkay.evaluate_settings(ignore_import_errors=False) 26 | # monkey-patch app so you can use edgy shell 27 | monkay.set_instance(Instance(registry=registry, app=app)) 28 | return app 29 | 30 | 31 | app = get_application() 32 | -------------------------------------------------------------------------------- /docs_src/connections/contextmanager_with_loop_and_cleanup.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from edgy import Registry, Instance, monkay, run_sync 3 | 4 | models = Registry(database="sqlite:///db.sqlite", echo=True) 5 | 6 | # load settings 7 | monkay.evaluate_settings(ignore_import_errors=False) 8 | # monkey-patch app so you can use edgy shell 9 | monkay.set_instance(Instance(registry=registry)) 10 | 11 | loop = asyncio.new_event_loop() 12 | with models.with_loop(loop): 13 | edgy.run_sync(User.query.all()) 14 | 15 | # uses the same loop 16 | with models.with_loop(loop): 17 | edgy.run_sync(User.query.all()) 18 | 19 | 20 | loop.run_until_complete(loop.shutdown_asyncgens()) 21 | loop.close() 22 | -------------------------------------------------------------------------------- /docs_src/connections/django.py: -------------------------------------------------------------------------------- 1 | from django.core.asgi import get_asgi_application 2 | 3 | 4 | from edgy import Registry, Instance 5 | 6 | models = Registry(database="sqlite:///db.sqlite", echo=True) 7 | 8 | 9 | application = models.asgi(handle_lifespan=True)(get_asgi_application()) 10 | 11 | # load settings 12 | monkay.evaluate_settings(ignore_import_errors=False) 13 | # monkey-patch app so you can use edgy shell 14 | monkay.set_instance(Instance(registry=registry, app=app)) 15 | -------------------------------------------------------------------------------- /docs_src/connections/manual.py: -------------------------------------------------------------------------------- 1 | from edgy import Registry, Instance, monkay 2 | 3 | models = Registry(database="sqlite:///db.sqlite", echo=True) 4 | 5 | 6 | async def main(): 7 | # load settings 8 | monkay.evaluate_settings(ignore_import_errors=False) 9 | # monkey-patch app so you can use edgy shell 10 | monkay.set_instance(Instance(app=app, registry=registry)) 11 | await models.__aenter__() 12 | try: 13 | ... 14 | finally: 15 | await models.__aexit__() 16 | -------------------------------------------------------------------------------- /docs_src/connections/manual_esmerald.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | from esmerald import Esmerald 3 | 4 | from edgy import Registry, Instance, monkay 5 | 6 | models = Registry(database="sqlite:///db.sqlite", echo=True) 7 | 8 | 9 | app = Esmerald( 10 | routes=[...], 11 | on_startup=[models.__aenter__], 12 | on_shutdown=[models.__aexit__], 13 | ) 14 | # load settings 15 | monkay.evaluate_settings(ignore_import_errors=False) 16 | # monkey-patch app so you can use edgy shell 17 | monkay.set_instance(Instance(app=app, registry=registry)) 18 | -------------------------------------------------------------------------------- /docs_src/connections/simple.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | from esmerald import Esmerald 3 | 4 | from edgy import Registry, Instance, monkay 5 | 6 | models = Registry(database="sqlite:///db.sqlite", echo=True) 7 | 8 | 9 | @asynccontextmanager 10 | async def lifespan(app: Esmerald): 11 | async with models: 12 | yield 13 | 14 | 15 | app = Esmerald( 16 | routes=[...], 17 | lifespan=lifespan, 18 | ) 19 | # now required 20 | monkay.evaluate_settings() 21 | # monkey-patch app so you can use edgy shell 22 | monkay.set_instance(Instance(app=app, registry=registry)) 23 | -------------------------------------------------------------------------------- /docs_src/contenttypes/basic.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | database = edgy.Database("sqlite:///db.sqlite") 4 | models = edgy.Registry(database=database, with_content_type=True) 5 | 6 | 7 | class Person(edgy.Model): 8 | first_name = edgy.fields.CharField(max_length=100) 9 | last_name = edgy.fields.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | unique_together = [("first_name", "last_name")] 14 | 15 | 16 | class Organisation(edgy.Model): 17 | name = edgy.fields.CharField(max_length=100, unique=True) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class Company(edgy.Model): 24 | name = edgy.fields.CharField(max_length=100, unique=True) 25 | 26 | class Meta: 27 | registry = models 28 | 29 | 30 | async def main(): 31 | async with database: 32 | await models.create_all() 33 | person = await Person.query.create(first_name="John", last_name="Doe") 34 | org = await Organisation.query.create(name="Edgy org") 35 | comp = await Company.query.create(name="Edgy inc") 36 | # we all have the content_type attribute and are queryable 37 | assert await models.content_type.query.count() == 3 38 | 39 | 40 | edgy.run_sync(main()) 41 | -------------------------------------------------------------------------------- /docs_src/contenttypes/m2m_with_contenttypes.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.contenttypes import ContentTypeField 3 | 4 | database = edgy.Database("sqlite:///db.sqlite") 5 | models = edgy.Registry(database=database, with_content_type=True) 6 | 7 | 8 | class PersonsOrganisations(edgy.Model): 9 | content_type = ContentTypeField() 10 | # to and from fields will be autogenerated 11 | 12 | class Meta: 13 | abstract = True 14 | 15 | 16 | class Person(edgy.Model): 17 | first_name = edgy.fields.CharField(max_length=100) 18 | last_name = edgy.fields.CharField(max_length=100) 19 | 20 | class Meta: 21 | registry = models 22 | unique_together = [("first_name", "last_name")] 23 | 24 | 25 | class Organisation(edgy.Model): 26 | name = edgy.fields.CharField(max_length=100, unique=True) 27 | persons = edgy.fields.ManyToMany( 28 | to=Person, through=PersonsOrganisations, through_tablename=edgy.NEW_M2M_NAMING 29 | ) 30 | 31 | class Meta: 32 | registry = models 33 | -------------------------------------------------------------------------------- /docs_src/extensions/add_extension.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import edgy 4 | 5 | 6 | @dataclass 7 | class Extension(ExtensionProtocol[edgy.Instance, edgy.Registry]): 8 | name: str = "hard-typed" 9 | 10 | def apply(self, monkay_instance: Monkay[edgy.Instance, edgy.Registry]) -> None: 11 | """Do something here""" 12 | 13 | 14 | @dataclass 15 | class ExtensionLessTyped(ExtensionProtocol): 16 | name: str = "less-typed" 17 | 18 | def apply(self, monkay_instance: Monkay) -> None: 19 | """Do something here""" 20 | 21 | 22 | edgy.monkay.add_extension(Extension()) 23 | 24 | edgy.monkay.add_extension(ExtensionLessTyped()) 25 | -------------------------------------------------------------------------------- /docs_src/extensions/settings.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from edgy import EdgySettings 3 | 4 | 5 | @dataclass 6 | class Extension(ExtensionProtocol[edgy.Instance, edgy.Registry]): 7 | name: str = "hello" 8 | 9 | def apply(self, monkay_instance: Monkay[edgy.Instance, edgy.Registry]) -> None: 10 | """Do something here""" 11 | 12 | 13 | @dataclass 14 | class ExtensionLessTyped(ExtensionProtocol): 15 | name: str = "hello" 16 | 17 | def apply(self, monkay_instance: Monkay) -> None: 18 | """Do something here""" 19 | 20 | 21 | class ExtensionSettings(EdgySettings): 22 | extensions: list[Any] = [Extension(), ExtensionLessTyped()] 23 | -------------------------------------------------------------------------------- /docs_src/fields/files/file_with_size.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any 3 | from sqlalchemy import func 4 | 5 | import edgy 6 | from edgy.exceptions import FileOperationError 7 | 8 | models = edgy.Registry(database=...) 9 | 10 | 11 | class Document(edgy.StrictModel): 12 | file: edgy.files.FieldFile = edgy.fields.FileField(with_size=True) 13 | 14 | class Meta: 15 | registry = models 16 | 17 | 18 | async def main(): 19 | document = await Document.query.create(file=b"abc") 20 | document2 = await Document.query.create(file=b"aabc") 21 | document3 = await Document.query.create(file=b"aabcc") 22 | 23 | sum_of_size = await Document.database.fetch_val(func.sum(Document.columns.file_size)) 24 | -------------------------------------------------------------------------------- /docs_src/marshalls/exclude.py: -------------------------------------------------------------------------------- 1 | from tests.settings import DATABASE_URL 2 | 3 | import edgy 4 | from edgy.core.marshalls import Marshall 5 | from edgy.core.marshalls.config import ConfigMarshall 6 | from edgy.testclient import DatabaseTestClient as Database 7 | 8 | database = Database(url=DATABASE_URL) 9 | registry = edgy.Registry(database=database) 10 | 11 | 12 | class User(edgy.Model): 13 | name: str = edgy.CharField(max_length=100, null=False) 14 | email: str = edgy.EmailField(max_length=100, null=False) 15 | language: str = edgy.CharField(max_length=200, null=True) 16 | description: str = edgy.TextField(max_length=5000, null=True) 17 | 18 | class Meta: 19 | registry = registry 20 | 21 | 22 | class UserMarshall(Marshall): 23 | marshall_config: ConfigMarshall = ConfigMarshall( 24 | model=User, 25 | exclude=["language"], 26 | ) 27 | -------------------------------------------------------------------------------- /docs_src/marshalls/fields.py: -------------------------------------------------------------------------------- 1 | from tests.settings import DATABASE_URL 2 | 3 | import edgy 4 | from edgy.core.marshalls import Marshall 5 | from edgy.core.marshalls.config import ConfigMarshall 6 | from edgy.testclient import DatabaseTestClient as Database 7 | 8 | database = Database(url=DATABASE_URL) 9 | registry = edgy.Registry(database=database) 10 | 11 | 12 | class User(edgy.Model): 13 | name: str = edgy.CharField(max_length=100, null=False) 14 | email: str = edgy.EmailField(max_length=100, null=False) 15 | language: str = edgy.CharField(max_length=200, null=True) 16 | description: str = edgy.TextField(max_length=5000, null=True) 17 | 18 | class Meta: 19 | registry = registry 20 | 21 | 22 | class UserMarshall(Marshall): 23 | marshall_config: ConfigMarshall = ConfigMarshall( 24 | model=User, 25 | fields=["name", "email"], 26 | ) 27 | -------------------------------------------------------------------------------- /docs_src/migrations/accounts_models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from my_project.utils import get_db_connection 4 | 5 | import edgy 6 | 7 | registry = get_db_connection() 8 | 9 | 10 | class User(edgy.Model): 11 | """ 12 | Base model for a user 13 | """ 14 | 15 | first_name: str = edgy.CharField(max_length=150) 16 | last_name: str = edgy.CharField(max_length=150) 17 | username: str = edgy.CharField(max_length=150, unique=True) 18 | email: str = edgy.EmailField(max_length=120, unique=True) 19 | password: str = edgy.CharField(max_length=128) 20 | last_login: datetime = edgy.DateTimeField(null=True) 21 | is_active: bool = edgy.BooleanField(default=True) 22 | is_staff: bool = edgy.BooleanField(default=False) 23 | is_superuser: bool = edgy.BooleanField(default=False) 24 | 25 | class Meta: 26 | registry = registry 27 | -------------------------------------------------------------------------------- /docs_src/migrations/attaching.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from edgy import EdgySettings 4 | 5 | 6 | class MyMigrationSettings(EdgySettings): 7 | # here we notify about the models import path 8 | preloads: list[str] = ["myproject.apps.accounts.models"] 9 | # here we can set the databases which should be used in migrations, by default (None,) 10 | migrate_databases: Union[list[Union[str, None]], tuple[Union[str, None], ...]] = (None,) 11 | -------------------------------------------------------------------------------- /docs_src/migrations/automigrations_library.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from monkay import ExtensionProtocol 3 | from edgy import Registry, EdgySettings 4 | 5 | 6 | class User(edgy.Model): 7 | age: int = edgy.IntegerField(gte=18) 8 | is_active: bool = edgy.BooleanField(default=True) 9 | 10 | 11 | class AddUserExtension(ExtensionProtocol): 12 | name = "add_user" 13 | 14 | def apply(self, monkay_instance): 15 | User.add_to_registry(monkay_instance.registry) 16 | 17 | 18 | class LibraryConfig(EdgySettings): 19 | extensions = [AddUserExtension()] 20 | 21 | 22 | async def create_custom_registry(): 23 | return Registry("DB_URL", automigrate_config=LibraryConfig) 24 | 25 | 26 | def get_application(): ... 27 | 28 | 29 | app = create_custom_registry().asgi(get_application()) 30 | -------------------------------------------------------------------------------- /docs_src/migrations/automigrations_library_disabled.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from monkay import ExtensionProtocol 3 | from edgy import Registry, EdgySettings 4 | 5 | 6 | class User(edgy.Model): 7 | age: int = edgy.IntegerField(gte=18) 8 | is_active: bool = edgy.BooleanField(default=True) 9 | 10 | 11 | class AddUserExtension(ExtensionProtocol): 12 | name = "add_user" 13 | 14 | def apply(self, monkay_instance): 15 | User.add_to_registry(monkay_instance.registry) 16 | 17 | 18 | class LibraryConfig(EdgySettings): 19 | extensions = [AddUserExtension()] 20 | 21 | 22 | class Config(EdgySettings): 23 | allow_automigrations = False 24 | 25 | 26 | async def create_custom_registry(): 27 | return Registry("DB_URL", automigrate_config=LibraryConfig) 28 | 29 | 30 | def get_application(): 31 | edgy.monkay.settings = Config 32 | return Registry("DB_URL").asgi(...) 33 | 34 | 35 | app = create_custom_registry().asgi(get_application()) 36 | 37 | # get sql migrations with 38 | # edgy -d library/migrations_folder migrate --sql 39 | # this is also the way for downgrades as automigrations does only work for upgrades 40 | -------------------------------------------------------------------------------- /docs_src/migrations/automigrations_main.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from monkay import ExtensionProtocol 3 | from edgy import Registry, EdgySettings 4 | 5 | 6 | class User(edgy.Model): 7 | age: int = edgy.IntegerField(gte=18) 8 | is_active: bool = edgy.BooleanField(default=True) 9 | 10 | 11 | class AddUserExtension(ExtensionProtocol): 12 | name = "add_user" 13 | 14 | def apply(self, monkay_instance): 15 | User.add_to_registry(monkay_instance.registry) 16 | 17 | 18 | class LibraryConfig(EdgySettings): 19 | extensions = [AddUserExtension()] 20 | 21 | 22 | class Config(EdgySettings): 23 | extensions = [AddUserExtension()] 24 | 25 | 26 | async def create_custom_registry(): 27 | return Registry("DB_URL", automigrate_config=LibraryConfig) 28 | 29 | 30 | def get_application(): 31 | edgy.monkay.settings = Config 32 | return Registry("DB_URL").asgi(...) 33 | 34 | 35 | app = get_application() 36 | -------------------------------------------------------------------------------- /docs_src/migrations/fastapi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | from fastapi import FastAPI 7 | from my_project.utils import get_db_connection 8 | 9 | 10 | def build_path(): 11 | """ 12 | Builds the path of the project and project root. 13 | """ 14 | Path(__file__).resolve().parent.parent 15 | SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) 16 | 17 | if SITE_ROOT not in sys.path: 18 | sys.path.append(SITE_ROOT) 19 | sys.path.append(os.path.join(SITE_ROOT, "apps")) 20 | 21 | 22 | def get_application(): 23 | """ 24 | Encapsulating in methods can be useful for controlling the import order but is optional. 25 | """ 26 | # first call build_path 27 | build_path() 28 | # because edgy tries to load settings eagerly 29 | from edgy import Instance, monkay 30 | 31 | registry = get_db_connection() 32 | 33 | # ensure the settings are loaded 34 | monkay.evaluate_settings(ignore_import_errors=False) 35 | 36 | app = registry.asgi(FastAPI(__name__)) 37 | 38 | monkay.set_instance(Instance(registry=registry, app=app)) 39 | return app 40 | 41 | 42 | app = get_application() 43 | -------------------------------------------------------------------------------- /docs_src/migrations/lru.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | 4 | @lru_cache() 5 | def get_db_connection(): 6 | from edgy import Registry 7 | 8 | # use echo=True for getting the connection infos printed, extra kwargs are passed to main database 9 | return Registry("postgresql+asyncpg://user:pass@localhost:5432/my_database", echo=True) 10 | -------------------------------------------------------------------------------- /docs_src/migrations/model.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from my_project.utils import get_db_connection 4 | 5 | import edgy 6 | 7 | registry = get_db_connection() 8 | 9 | 10 | class User(edgy.Model): 11 | """ 12 | Base model for a user 13 | """ 14 | 15 | first_name: str = edgy.CharField(max_length=150) 16 | last_name: str = edgy.CharField(max_length=150) 17 | username: str = edgy.CharField(max_length=150, unique=True) 18 | email: str = edgy.EmailField(max_length=120, unique=True) 19 | password: str = edgy.CharField(max_length=128) 20 | last_login: datetime = edgy.DateTimeField(null=True) 21 | is_active: bool = edgy.BooleanField(default=True) 22 | is_staff: bool = edgy.BooleanField(default=False) 23 | is_superuser: bool = edgy.BooleanField(default=False) 24 | 25 | class Meta: 26 | registry = registry 27 | -------------------------------------------------------------------------------- /docs_src/migrations/starlette.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | from starlette.applications import Starlette 7 | from my_project.utils import get_db_connection 8 | 9 | 10 | def build_path(): 11 | """ 12 | Builds the path of the project and project root. 13 | """ 14 | Path(__file__).resolve().parent.parent 15 | SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) 16 | 17 | if SITE_ROOT not in sys.path: 18 | sys.path.append(SITE_ROOT) 19 | sys.path.append(os.path.join(SITE_ROOT, "apps")) 20 | 21 | 22 | def get_application(): 23 | """ 24 | Encapsulating in methods can be useful for controlling the import order but is optional. 25 | """ 26 | # first call build_path 27 | build_path() 28 | # because edgy tries to load settings eagerly 29 | from edgy import monkay, Instance 30 | 31 | registry = get_db_connection() 32 | # ensure the settings are loaded 33 | monkay.evaluate_settings(ignore_import_errors=False) 34 | 35 | app = registry.asgi(Starlette()) 36 | 37 | monkay.set_instance(Instance(registry=registry, app=app)) 38 | return app 39 | 40 | 41 | app = get_application() 42 | -------------------------------------------------------------------------------- /docs_src/models/abstract/simple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class BaseModel(edgy.Model): 9 | class Meta: 10 | abstract = True 11 | registry = models 12 | -------------------------------------------------------------------------------- /docs_src/models/constraints.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | import sqlalchemy 3 | from edgy import Database, Registry 4 | 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class User(edgy.Model): 11 | name = edgy.fields.CharField(max_length=255) 12 | is_admin = edgy.fields.BooleanField(default=False) 13 | age = edgy.IntegerField(null=True) 14 | 15 | class Meta: 16 | registry = models 17 | constraints = [sqlalchemy.CheckConstraint("age > 13 OR is_admin = true", name="user_age")] 18 | -------------------------------------------------------------------------------- /docs_src/models/declarative/example.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | # Declare the Edgy model 9 | 10 | 11 | class User(edgy.Model): 12 | is_active: bool = edgy.BooleanField(default=True) 13 | first_name: str = edgy.CharField(max_length=50) 14 | last_name: str = edgy.CharField(max_length=50) 15 | email: str = edgy.EmailField(max_lengh=100) 16 | password: str = edgy.CharField(max_length=1000) 17 | 18 | class Meta: 19 | registry = models 20 | 21 | 22 | # Generate the declarative version 23 | UserModelDeclarative = User.declarative() 24 | -------------------------------------------------------------------------------- /docs_src/models/declarative/fk_relationship.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=True) 10 | first_name: str = edgy.CharField(max_length=50) 11 | last_name: str = edgy.CharField(max_length=50) 12 | email: str = edgy.EmailField(max_lengh=100) 13 | password: str = edgy.CharField(max_length=1000) 14 | 15 | class Meta: 16 | registry = models 17 | 18 | 19 | class Thread(edgy.Model): 20 | sender: User = edgy.ForeignKey( 21 | User, 22 | on_delete=edgy.CASCADE, 23 | related_name="sender", 24 | ) 25 | receiver: User = edgy.ForeignKey( 26 | User, 27 | on_delete=edgy.CASCADE, 28 | related_name="receiver", 29 | ) 30 | message: str = edgy.TextField() 31 | 32 | class Meta: 33 | registry = models 34 | -------------------------------------------------------------------------------- /docs_src/models/declaring_models.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | The user model representation. 11 | 12 | Note: For sqlite the BigIntegerField transforms in an IntegerField when autoincrement is set 13 | because sqlite doesn't support BigInteger autoincrement fields. 14 | """ 15 | 16 | id: int = edgy.BigIntegerField(primary_key=True, autoincrement=True) 17 | name: str = edgy.CharField(max_length=255) 18 | age: int = edgy.IntegerField(minimum=18) 19 | is_active: bool = edgy.BooleanField(default=True) 20 | 21 | class Meta: 22 | registry = models 23 | -------------------------------------------------------------------------------- /docs_src/models/declaring_models_no_id.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | The user model representation. 11 | 12 | The `id` is not provided and Edgy will automatically 13 | generate a primary_key `id` BigIntegerField. 14 | """ 15 | 16 | name: str = edgy.CharField(max_length=255) 17 | age: int = edgy.IntegerField(gte=18) 18 | is_active: bool = edgy.BooleanField(default=True) 19 | 20 | class Meta: 21 | registry = models 22 | -------------------------------------------------------------------------------- /docs_src/models/declaring_models_pk_no_id.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class User(edgy.Model): 11 | non_default_id = edgy.BigIntegerField(primary_key=True, autoincrement=True) 12 | name: str = edgy.CharField(max_length=255, primary_key=True) 13 | age: int = edgy.IntegerField(gte=18) 14 | is_active: bool = edgy.BooleanField(default=True) 15 | 16 | class Meta: 17 | registry = models 18 | -------------------------------------------------------------------------------- /docs_src/models/default_model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | age: int = edgy.IntegerField(minimum=18) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | registry = models 14 | -------------------------------------------------------------------------------- /docs_src/models/indexes/complex_together.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Index, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | status: str = edgy.CharField(max_length=255) 13 | 14 | class Meta: 15 | registry = models 16 | indexes = [ 17 | Index(fields=["name", "email"]), 18 | Index(fields=["is_active", "status"], name="active_status_idx"), 19 | ] 20 | -------------------------------------------------------------------------------- /docs_src/models/indexes/simple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70, index=True) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/models/indexes/simple2.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Index, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | indexes = [Index(fields=["email"])] 16 | -------------------------------------------------------------------------------- /docs_src/models/managers/example.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import edgy 4 | from edgy import Manager, QuerySet 5 | 6 | 7 | class InactiveManager(Manager): 8 | """ 9 | Custom manager that will return only active users 10 | """ 11 | 12 | def get_queryset(self) -> "QuerySet": 13 | queryset = super().get_queryset().filter(is_active=False) 14 | return queryset 15 | 16 | 17 | class User(edgy.Model): 18 | # Add the new manager 19 | inactives: ClassVar[Manager] = InactiveManager() 20 | -------------------------------------------------------------------------------- /docs_src/models/managers/simple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | unique_together = [("name", "email")] 16 | 17 | 18 | # Using ipython that supports await 19 | # Don't use this in production! Use Alembic or any tool to manage 20 | # The migrations for you 21 | await models.create_all() # noqa 22 | 23 | await User.query.create(name="Edgy", email="foo@bar.com") # noqa 24 | 25 | user = await User.query.get(id=1) # noqa 26 | # User(id=1) 27 | -------------------------------------------------------------------------------- /docs_src/models/on_conflict.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | models = ... 4 | 5 | 6 | class Foo(edgy.Model, on_conflict="keep"): 7 | class Meta: 8 | registry = models 9 | 10 | 11 | # or 12 | 13 | 14 | class Foo2(edgy.Model): 15 | class Meta: 16 | registry = False 17 | 18 | 19 | Foo2.add_to_registry(models, name="Foo", on_conflict="replace") 20 | -------------------------------------------------------------------------------- /docs_src/models/pk_no_default.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | id: int = edgy.CharField(max_length=255, primary_key=True) 10 | age: int = edgy.IntegerField(minimum=18) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | obj = edgy.run_sync(User.query.create(id="edgy", age=19)) 18 | -------------------------------------------------------------------------------- /docs_src/models/pk_with_default.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class User(edgy.Model): 11 | id: int = edgy.UUIDField(primary_key=True, default=uuid.uuid4) 12 | age: int = edgy.IntegerField(minimum=18) 13 | is_active: bool = edgy.BooleanField(default=True) 14 | 15 | class Meta: 16 | registry = models 17 | -------------------------------------------------------------------------------- /docs_src/models/registry/inheritance_abstract.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class BaseModel(edgy.Model): 9 | """ 10 | The base model for all models using the `models` registry. 11 | """ 12 | 13 | class Meta: 14 | abstract = True 15 | registry = models 16 | 17 | 18 | class User(BaseModel): 19 | name: str = edgy.CharField(max_length=255) 20 | is_active: bool = edgy.BooleanField(default=True) 21 | 22 | 23 | class Product(BaseModel): 24 | user: User = edgy.ForeignKey(User, null=False, on_delete=edgy.CASCADE) 25 | sku: str = edgy.CharField(max_length=255, null=False) 26 | -------------------------------------------------------------------------------- /docs_src/models/registry/inheritance_no_repeat.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class BaseModel(edgy.Model): 9 | """ 10 | The base model for all models using the `models` registry. 11 | """ 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | class User(BaseModel): 18 | nametr = edgy.CharField(max_length=255) 19 | is_active: bool = edgy.BooleanField(default=True) 20 | 21 | 22 | class Product(BaseModel): 23 | user: User = edgy.ForeignKey(User, null=False, on_delete=edgy.CASCADE) 24 | sku: str = edgy.CharField(max_length=255, null=False) 25 | -------------------------------------------------------------------------------- /docs_src/models/registry/nutshell.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | registry = models 14 | -------------------------------------------------------------------------------- /docs_src/models/tablename/model_diff_tn.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | tablename = "db_users" 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/models/tablename/model_no_tablename.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | If the `tablename` is not declared in the `Meta`, 11 | edgy will pluralise the class name. 12 | 13 | This table will be called in the database `users`. 14 | """ 15 | 16 | name: str = edgy.CharField(max_length=255) 17 | is_active: bool = edgy.BooleanField(default=True) 18 | 19 | class Meta: 20 | registry = models 21 | -------------------------------------------------------------------------------- /docs_src/models/tablename/model_with_tablename.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | tablename = "users" 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/complex_combined.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | phone_number: str = edgy.CharField(max_length=15) 12 | address: str = edgy.CharField(max_length=500) 13 | is_active: bool = edgy.BooleanField(default=True) 14 | 15 | class Meta: 16 | registry = models 17 | unique_together = [ 18 | ("name", "email"), 19 | ("name", "email", "phone_number"), 20 | ("email", "address"), 21 | ] 22 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/complex_independent.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | unique_together = ["name", "email"] 16 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/complex_mixed.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | phone_number: str = edgy.CharField(max_length=15) 12 | address: str = edgy.CharField(max_length=500) 13 | is_active: bool = edgy.BooleanField(default=True) 14 | 15 | class Meta: 16 | registry = models 17 | unique_together = [ 18 | ("name", "email"), 19 | ("name", "email", "phone_number"), 20 | ("email", "address"), 21 | "is_active", 22 | ] 23 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/complex_together.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | unique_together = [("name", "email")] 16 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/constraints/complex.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry, UniqueConstraint 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | phone_number: str = edgy.CharField(max_length=15) 12 | address: str = edgy.CharField(max_length=500) 13 | is_active: bool = edgy.BooleanField(default=True) 14 | 15 | class Meta: 16 | registry = models 17 | unique_together = [ 18 | UniqueConstraint(fields=["name", "email"]), 19 | UniqueConstraint(fields=["name", "email", "phone_number"]), 20 | UniqueConstraint(fields=["email", "address"]), 21 | ] 22 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/constraints/mixing.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry, UniqueConstraint 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | phone_number: str = edgy.CharField(max_length=15) 12 | address: str = edgy.CharField(max_length=500) 13 | is_active: bool = edgy.BooleanField(default=True) 14 | 15 | class Meta: 16 | registry = models 17 | unique_together = [ 18 | UniqueConstraint(fields=["name", "email"]), 19 | ("name", "email", "phone_number"), 20 | ("email", "address"), 21 | ] 22 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/simple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70, unique=True) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/models/unique_together/simple2.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.EmailField(max_length=70) 11 | is_active: bool = edgy.BooleanField(default=True) 12 | 13 | class Meta: 14 | registry = models 15 | unique_together = ["name"] # or ("name",) 16 | -------------------------------------------------------------------------------- /docs_src/permissions/passwordargon2id.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | from argon2 import PasswordHasher 4 | 5 | hasher = PasswordHasher() 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | # derive_fn implies the original password is saved in _original 11 | pw: str = edgy.PasswordField(null=False, derive_fn=hasher.hash) 12 | ... 13 | 14 | 15 | user = await User.query.create(name="foo", pw="foo") 16 | 17 | provided_pw = "foo" 18 | # comparing 19 | if not hasher.verify(provided_pw, user.pw): 20 | raise 21 | 22 | # rehash 23 | if hasher.check_needs_rehash(user.pw): 24 | user.pw = provided_pw 25 | await user.save() 26 | -------------------------------------------------------------------------------- /docs_src/permissions/passwordfield_basic.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | import secrets 3 | from contextlib import suppress 4 | 5 | hasher = Hasher() 6 | 7 | 8 | class User(edgy.Model): 9 | pw: str = edgy.PasswordField(null=False, derive_fn=hasher.derive) 10 | token: str = edgy.PasswordField(null=False, default=secrets.token_hex) 11 | ... 12 | 13 | 14 | # we can check if the pw matches by providing a tuple 15 | with suppress(Exception): 16 | # oops, doesn't work 17 | obj = await User.query.create(pw=("foobar", "notfoobar")) 18 | obj = await User.query.create(pw=("foobar", "foobar")) 19 | # now let's check the pw 20 | hasher.compare_pw(obj.pw, "foobar") 21 | # now let's compare the token safely 22 | secrets.compare_digest(obj.token, "") 23 | -------------------------------------------------------------------------------- /docs_src/permissions/passwordfield_token.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | import secrets 3 | from contextlib import suppress 4 | 5 | 6 | class Computer(edgy.Model): 7 | token: str = edgy.PasswordField(null=False, default=secrets.token_hex) 8 | ... 9 | 10 | 11 | obj = await Computer.query.create() 12 | # now let's compare the token safely 13 | secrets.compare_digest(obj.token, "") 14 | -------------------------------------------------------------------------------- /docs_src/permissions/passwordpasslib.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | from passlib.context import CryptContext 4 | from pydantic import ValidationError 5 | 6 | pwd_context = CryptContext( 7 | # Replace this list with the hash(es) you wish to support. 8 | schemes=["argon2", "pbkdf2_sha256"], 9 | deprecated="auto", 10 | ) 11 | 12 | 13 | class User(edgy.Model): 14 | name: str = edgy.CharField(max_length=255) 15 | # derive_fn implies the original password is saved in _original 16 | pw: str = edgy.PasswordField(null=False, derive_fn=pwd_context.hash) 17 | ... 18 | 19 | 20 | user = await User.query.create(name="foo", pw="foo") 21 | 22 | provided_pw = "foo" 23 | # comparing 24 | if not pwd_context.verify(provided_pw, user.pw): 25 | raise 26 | 27 | 28 | if pwd_context.needs_update(user.pw): 29 | user.pw = provided_pw 30 | await user.save() 31 | -------------------------------------------------------------------------------- /docs_src/permissions/quickstart.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.permissions import BasePermission 3 | 4 | models = edgy.Registry("sqlite:///foo.sqlite3") 5 | 6 | 7 | class User(edgy.Model): 8 | name = edgy.fields.CharField(max_length=100, unique=True) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class Permission(BasePermission): 15 | users = edgy.fields.ManyToMany( 16 | "User", embed_through=False, through_tablename=edgy.NEW_M2M_NAMING 17 | ) 18 | 19 | class Meta: 20 | registry = models 21 | unique_together = [("name",)] 22 | 23 | 24 | user = User.query.create(name="edgy") 25 | permission = await Permission.query.create(users=[user], name="view") 26 | assert await Permission.query.users("view").get() == user 27 | await Permission.query.permissions_of(user) 28 | -------------------------------------------------------------------------------- /docs_src/prefetch/first/asserting.py: -------------------------------------------------------------------------------- 1 | assert len(users) == 2 # Total ussers 2 | 3 | edgy = [value for value in users if value.pk == edgy.pk][0] 4 | assert len(edgy.to_posts) == 5 # Total posts for Edgy 5 | assert len(edgy.to_articles) == 50 # Total articles for Edgy 6 | 7 | esmerald = [value for value in users if value.pk == esmerald.pk][0] 8 | assert len(esmerald.to_posts) == 15 # Total posts for Esmerald 9 | assert len(esmerald.to_articles) == 20 # Total articles for Esmerald 10 | -------------------------------------------------------------------------------- /docs_src/prefetch/first/data.py: -------------------------------------------------------------------------------- 1 | user = await User.query.create(name="Edgy") 2 | 3 | for i in range(5): 4 | await Post.query.create(comment="Comment number %s" % i, user=user) 5 | 6 | for i in range(50): 7 | await Article.query.create(content="Comment number %s" % i, user=user) 8 | 9 | esmerald = await User.query.create(name="Esmerald") 10 | 11 | for i in range(15): 12 | await Post.query.create(comment="Comment number %s" % i, user=esmerald) 13 | 14 | for i in range(20): 15 | await Article.query.create(content="Comment number %s" % i, user=esmerald) 16 | -------------------------------------------------------------------------------- /docs_src/prefetch/first/models.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | database = edgy.Database("sqlite:///db.sqlite") 4 | models = edgy.Registry(database=database) 5 | 6 | 7 | class User(edgy.Model): 8 | name = edgy.CharField(max_length=100) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class Post(edgy.Model): 15 | user = edgy.ForeignKey(User, related_name="posts") 16 | comment = edgy.CharField(max_length=255) 17 | 18 | class Meta: 19 | registry = models 20 | 21 | 22 | class Article(edgy.Model): 23 | user = edgy.ForeignKey(User, related_name="articles") 24 | content = edgy.CharField(max_length=255) 25 | 26 | class Meta: 27 | registry = models 28 | -------------------------------------------------------------------------------- /docs_src/prefetch/first/prefetch.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Prefetch 3 | 4 | database = edgy.Database("sqlite:///db.sqlite") 5 | models = edgy.Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Post(edgy.Model): 16 | user = edgy.ForeignKey(User, related_name="posts") 17 | comment = edgy.CharField(max_length=255) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class Article(edgy.Model): 24 | user = edgy.ForeignKey(User, related_name="articles") 25 | content = edgy.CharField(max_length=255) 26 | 27 | class Meta: 28 | registry = models 29 | 30 | 31 | # All the users with all the posts and articles 32 | # of each user 33 | users = await User.query.prefetch_related( 34 | Prefetch(related_name="posts", to_attr="to_posts"), 35 | Prefetch(related_name="articles", to_attr="to_articles"), 36 | ).all() 37 | -------------------------------------------------------------------------------- /docs_src/prefetch/second/data.py: -------------------------------------------------------------------------------- 1 | # Create the album 2 | album = await Album.query.create(name="Malibu") 3 | 4 | # Create the track 5 | await Track.query.create(album=album, title="The Bird", position=1) 6 | 7 | # Create the studio 8 | studio = await Studio.query.create(album=album, name="Valentim") 9 | 10 | # Create the company 11 | await Company.query.create(studio=studio) 12 | -------------------------------------------------------------------------------- /docs_src/prefetch/second/models.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | database = edgy.Database("sqlite:///db.sqlite") 4 | models = edgy.Registry(database=database) 5 | 6 | 7 | class Album(edgy.Model): 8 | id = edgy.IntegerField(primary_key=True, autoincrement=True) 9 | name = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Track(edgy.Model): 16 | id = edgy.BigIntegerField(primary_key=True, autoincrement=True) 17 | album = edgy.ForeignKey("Album", on_delete=edgy.CASCADE, related_name="tracks") 18 | title = edgy.CharField(max_length=100) 19 | position = edgy.IntegerField() 20 | 21 | class Meta: 22 | registry = models 23 | 24 | 25 | class Studio(edgy.Model): 26 | album = edgy.ForeignKey("Album", related_name="studios") 27 | name = edgy.CharField(max_length=255) 28 | 29 | class Meta: 30 | registry = models 31 | 32 | 33 | class Company(edgy.Model): 34 | studio = edgy.ForeignKey(Studio, related_name="companies") 35 | 36 | class Meta: 37 | registry = models 38 | -------------------------------------------------------------------------------- /docs_src/prefetch/second/prefetch.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Prefetch 3 | 4 | database = edgy.Database("sqlite:///db.sqlite") 5 | models = edgy.Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Post(edgy.Model): 16 | user = edgy.ForeignKey(User, related_name="posts") 17 | comment = edgy.CharField(max_length=255) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class Article(edgy.Model): 24 | user = edgy.ForeignKey(User, related_name="articles") 25 | content = edgy.CharField(max_length=255) 26 | 27 | class Meta: 28 | registry = models 29 | 30 | 31 | # All the tracks that belong to a specific `Company`. 32 | # The tracks are associated with `albums` and `studios` 33 | company = await Company.query.prefetch_related( 34 | Prefetch(related_name="studio__album__tracks", to_attr="tracks") 35 | ).get(studio=studio) 36 | -------------------------------------------------------------------------------- /docs_src/prefetch/second/prefetch_filtered.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Prefetch 3 | 4 | database = edgy.Database("sqlite:///db.sqlite") 5 | models = edgy.Registry(database=database) 6 | 7 | # All the tracks that belong to a specific `Company`. 8 | # The tracks are associated with `albums` and `studios` 9 | # where the `Track` will be also internally filtered 10 | company = await Company.query.prefetch_related( 11 | Prefetch( 12 | related_name="studio__album__tracks", 13 | to_attr="tracks", 14 | queryset=Track.query.filter(title__icontains="bird"), 15 | ) 16 | ) 17 | -------------------------------------------------------------------------------- /docs_src/queries/annotate/child_annotate.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | 3 | import edgy 4 | 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.Model): 9 | name = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Profile(edgy.Model): 16 | user = edgy.fields.OneToOne(User, related_name="profile") 17 | name = edgy.CharField(max_length=100) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | for profile in await Profile.query.select_related("profile").reference_select( 24 | {"user": {"profile_name": "name"}} 25 | ): 26 | assert profile.user.profile_name == profile.name 27 | 28 | # up to one level you can leave out the select_related() 29 | # you can also reference columns in case you use them of the main table or explicitly provided via extra_select 30 | 31 | for profile in await Profile.query.reference_select( 32 | {"user": {"profile_name": Profile.table.c.name}} 33 | ): 34 | assert profile.user.profile_name == profile.name 35 | -------------------------------------------------------------------------------- /docs_src/queries/annotate/child_annotate_embed.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | 3 | import edgy 4 | 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.Model): 9 | name = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Profile(edgy.Model): 16 | user = edgy.fields.OneToOne(User, related_name="profile") 17 | name = edgy.CharField(max_length=100) 18 | profile = edgy.fields.OneToOne( 19 | "SuperProfile", related_name="profile", embed_parent=("user", "normal_profile") 20 | ) 21 | 22 | class Meta: 23 | registry = models 24 | 25 | 26 | class SuperProfile(edgy.Model): 27 | name = edgy.CharField(max_length=100) 28 | 29 | class Meta: 30 | registry = models 31 | 32 | 33 | for profile in await SuperProfile.query.all(): 34 | user = ( 35 | await profile.profile.select_related("user") 36 | .reference_select({"user": {"profile_name": "name"}}) 37 | .get() 38 | ) 39 | assert isinstance(user, User) 40 | assert user.normal_profile.name == user.profile_name 41 | -------------------------------------------------------------------------------- /docs_src/queries/annotate/parent_annotate.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | 3 | import edgy 4 | from edgy.core.utils.db import hash_tablekey 5 | 6 | models = edgy.Registry(database=...) 7 | 8 | 9 | class User(edgy.Model): 10 | name = edgy.CharField(max_length=100) 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class Profile(edgy.Model): 17 | user = edgy.fields.OneToOne(User, related_name="profile") 18 | name = edgy.CharField(max_length=100) 19 | 20 | class Meta: 21 | registry = models 22 | 23 | 24 | for profile in await Profile.query.select_related("user").reference_select( 25 | {"user_name": sqlalchemy.text(f"user__name")} 26 | ): 27 | assert profile.user_name == profile.user.name 28 | 29 | 30 | # manual way 31 | join_table_key = hash_tablekey(tablekey=User.table.key, prefix="user") 32 | for profile in await Profile.query.select_related("user").reference_select( 33 | {"user_name": sqlalchemy.text(f"{join_table_key}_name")} 34 | ): 35 | assert profile.user_name == profile.user.name 36 | -------------------------------------------------------------------------------- /docs_src/queries/annotate/strict_child_annotate.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | 3 | import edgy 4 | 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.StrictModel): 9 | name = edgy.CharField(max_length=100) 10 | profile_name = edgy.fields.PlaceholderField(null=True) 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class Profile(edgy.Model): 17 | user = edgy.fields.OneToOne(User, related_name="profile") 18 | name = edgy.CharField(max_length=100) 19 | 20 | class Meta: 21 | registry = models 22 | 23 | 24 | for profile in await Profile.query.select_related("profile").reference_select( 25 | {"user": {"profile_name": "name"}} 26 | ): 27 | assert profile.user.profile_name == profile.name 28 | 29 | # up to one level you can leave out the select_related() 30 | 31 | for profile in await Profile.query.reference_select({"user": {"profile_name": "name"}}): 32 | assert profile.user.profile_name == profile.name 33 | -------------------------------------------------------------------------------- /docs_src/queries/annotate/subquery_annotate.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy 2 | from sqlalchemy import func 3 | 4 | import edgy 5 | 6 | models = edgy.Registry(database=...) 7 | 8 | 9 | class User(edgy.Model): 10 | name = edgy.CharField(max_length=100) 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class Profile(edgy.Model): 17 | user = edgy.fields.OneToOne(User, related_name="profile") 18 | name = edgy.CharField(max_length=100) 19 | 20 | class Meta: 21 | registry = models 22 | 23 | 24 | for profile in await Profile.query.extra_select( 25 | func.count() 26 | .select() 27 | .select_from((await User.query.as_select()).subquery()) 28 | .label("total_number") 29 | ).reference_select({"total_number": "total_number"}): 30 | assert profile.total_number == 10 31 | 32 | 33 | # or manually 34 | for profile in await Profile.query.extra_select( 35 | sqlalchemy.select(func.count(User.table.c.id).label("total_number")).subquery() 36 | ).reference_select({"total_number": "total_number"}): 37 | assert profile.total_number >= 0 38 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/and.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the and_ 9 | await User.query.filter( 10 | edgy.and_(User.columns.name == "Adam", User.columns.email == "adam@edgy.dev"), 11 | ) 12 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/and_m_filter.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the and_ 9 | await User.query.filter(edgy.and_(User.columns.name == "Adam")).filter( 10 | edgy.and_(User.columns.email == "adam@edgy.dev") 11 | ) 12 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/and_two.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the and_ 9 | await User.query.filter( 10 | edgy.and_( 11 | User.columns.email.contains("edgy"), 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | first_name: str = edgy.CharField(max_length=50, null=True) 10 | email: str = edgy.EmailField(max_lengh=100, null=True) 11 | 12 | class Meta: 13 | registry = models 14 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/not.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the not_ 9 | await User.query.filter(edgy.not_(User.columns.name == "Adam")) 10 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/not_m_filter.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | await User.query.create(name="John", email="john@example.com") 8 | 9 | # Query using the not_ 10 | await User.query.filter(edgy.not_(User.columns.name == "Adam")).filter( 11 | edgy.not_(User.columns.email.contains("edgy")) 12 | ) 13 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/not_two.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the not_ 9 | await User.query.filter( 10 | edgy.not_( 11 | User.columns.email.contains("edgy"), 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/or.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the or_ 9 | await User.query.filter( 10 | edgy.or_(User.columns.name == "Adam", User.columns.email == "adam@edgy.dev"), 11 | ) 12 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/or_m_filter.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the or_ 9 | await User.query.filter(edgy.or_(User.columns.name == "Adam")).filter( 10 | edgy.or_(User.columns.email == "adam@edgy.dev") 11 | ) 12 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/or_two.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the or_ 9 | await User.query.filter( 10 | edgy.or_( 11 | User.columns.email.contains("edgy"), 12 | ) 13 | ) 14 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/and.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the and_ 7 | await User.query.and_(name="Adam", email="adam@edgy.dev") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/and_m_filter.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the and_ 7 | await User.query.filter(name="Adam").and_(email="adam@edgy.dev") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/and_two.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the and_ 7 | await User.query.and_(email__icontains="edgy") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | first_name: str = edgy.CharField(max_length=50, null=True) 10 | email: str = edgy.EmailField(max_lengh=100, null=True) 11 | 12 | class Meta: 13 | registry = models 14 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/not.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the not_ 7 | await User.query.not_(name="Adam") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/not_m_filter.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | await User.query.create(name="John", email="john@example.com") 6 | 7 | # Query using the not_ 8 | await User.query.filter(email__icontains="edgy").not_(name__iexact="Adam") 9 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/not_two.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the not_ 7 | await User.query.not_(email__icontains="edgy").not_(name__icontains="a") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/or.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | # Create some records 4 | 5 | await User.query.create(name="Adam", email="adam@edgy.dev") 6 | await User.query.create(name="Eve", email="eve@edgy.dev") 7 | 8 | # Query using the global or_ with multiple ANDed field queries 9 | await User.query.or_(name="Adam", email="adam@edgy.dev") 10 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/or_m_filter.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the or_ 7 | await User.query.or_(name="Adam").filter(email="adam@edgy.dev") 8 | -------------------------------------------------------------------------------- /docs_src/queries/clauses/style/or_two.py: -------------------------------------------------------------------------------- 1 | # Create some records 2 | 3 | await User.query.create(name="Adam", email="adam@edgy.dev") 4 | await User.query.create(name="Eve", email="eve@edgy.dev") 5 | 6 | # Query using the multiple or_ 7 | await User.query.or_({"email__icontains": "edgy"}, {"name__icontains": "a"}) 8 | 9 | # QUery using the global or 10 | await User.query.or_(email__icontains="edgy").or_(name__icontains="a") 11 | 12 | # Query using the or_ with multiple ANDed field queries 13 | await User.query.or_(email__icontains="edgy", name__icontains="a") 14 | -------------------------------------------------------------------------------- /docs_src/queries/manytomany/example.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class Team(edgy.Model): 11 | name: str = edgy.CharField(max_length=100) 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | class Organisation(edgy.Model): 18 | ident: str = edgy.CharField(max_length=100) 19 | teams: List[Team] = edgy.ManyToManyField( 20 | Team, related_name="organisation_teams", through_tablename=edgy.NEW_M2M_NAMING 21 | ) 22 | 23 | class Meta: 24 | registry = models 25 | -------------------------------------------------------------------------------- /docs_src/queries/manytomany/no_rel.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class Team(edgy.Model): 11 | name: str = edgy.CharField(max_length=100) 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | class Organisation(edgy.Model): 18 | ident: str = edgy.CharField(max_length=100) 19 | teams: List[Team] = edgy.ManyToManyField(Team, through_tablename=edgy.NEW_M2M_NAMING) 20 | 21 | class Meta: 22 | registry = models 23 | -------------------------------------------------------------------------------- /docs_src/queries/manytomany/no_rel_query_example.py: -------------------------------------------------------------------------------- 1 | # Create some fake data 2 | blue_team = await Team.query.create(name="Blue Team") 3 | green_team = await Team.query.create(name="Green Team") 4 | 5 | # Add the teams to the organisation 6 | organisation = await Organisation.query.create(ident="Acme Ltd") 7 | await organisation.teams.add(blue_team) 8 | await organisation.teams.add(green_team) 9 | 10 | # Query 11 | await blue_team.team_organisationteams_set.filter(name=blue_team.name) 12 | -------------------------------------------------------------------------------- /docs_src/queries/manytomany/query_example.py: -------------------------------------------------------------------------------- 1 | # Create some fake data 2 | blue_team = await Team.query.create(name="Blue Team") 3 | green_team = await Team.query.create(name="Green Team") 4 | 5 | # Add the teams to the organisation 6 | organisation = await Organisation.query.create(ident="Acme Ltd") 7 | organisation.teams.add(blue_team) 8 | organisation.teams.add(green_team) 9 | 10 | # Query 11 | blue_team.organisation_teams.filter(name=blue_team.name) 12 | -------------------------------------------------------------------------------- /docs_src/queries/manytoone/example.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class Team(edgy.Model): 11 | name: str = edgy.fields.CharField(max_length=100) 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | class TeamMember(edgy.Model): 18 | name: str = edgy.fields.CharField(max_length=100) 19 | team: Team = edgy.fields.ForeignKey(Team, related_name="members") 20 | 21 | class Meta: 22 | registry = models 23 | -------------------------------------------------------------------------------- /docs_src/queries/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=True) 10 | first_name: str = edgy.CharField(max_length=50) 11 | last_name: str = edgy.CharField(max_length=50) 12 | email: str = edgy.EmailField(max_lengh=100) 13 | password: str = edgy.CharField(max_length=1000) 14 | 15 | class Meta: 16 | registry = models 17 | 18 | 19 | class Profile(edgy.Model): 20 | user: User = edgy.ForeignKey(User, on_delete=edgy.CASCADE) 21 | 22 | class Meta: 23 | registry = models 24 | -------------------------------------------------------------------------------- /docs_src/queries/related_name/data.py: -------------------------------------------------------------------------------- 1 | # This assumes you have the models imported 2 | # or accessible from somewhere allowing you to generate 3 | # these records in your database 4 | 5 | 6 | acme = await Organisation.query.create(ident="ACME Ltd") 7 | red_team = await Team.query.create(org=acme, name="Red Team") 8 | blue_team = await Team.query.create(org=acme, name="Blue Team") 9 | 10 | await Member.query.create(team=red_team, email="charlie@redteam.com") 11 | await Member.query.create(team=red_team, email="brown@redteam.com") 12 | await Member.query.create(team=blue_team, email="monica@blueteam.com") 13 | await Member.query.create(team=blue_team, email="snoopy@blueteam.com") 14 | -------------------------------------------------------------------------------- /docs_src/queries/related_name/example.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class Organisation(edgy.Model): 9 | ident: str = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Team(edgy.Model): 16 | org: Organisation = edgy.ForeignKey(Organisation, on_delete=edgy.RESTRICT) 17 | name: str = edgy.CharField(max_length=100) 18 | 19 | class Meta: 20 | registry = models 21 | -------------------------------------------------------------------------------- /docs_src/queries/related_name/models.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class Organisation(edgy.Model): 9 | ident: str = edgy.CharField(max_length=100) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Team(edgy.Model): 16 | org: Organisation = edgy.ForeignKey(Organisation, on_delete=edgy.RESTRICT) 17 | name: str = edgy.CharField(max_length=100) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class Member(edgy.Model): 24 | team: Team = edgy.ForeignKey(Team, on_delete=edgy.SET_NULL, null=True, related_name="members") 25 | second_team: Team = edgy.ForeignKey( 26 | Team, on_delete=edgy.SET_NULL, null=True, related_name="team_members" 27 | ) 28 | email: str = edgy.CharField(max_length=100) 29 | name: str = edgy.CharField(max_length=255, null=True) 30 | 31 | class Meta: 32 | registry = models 33 | -------------------------------------------------------------------------------- /docs_src/queries/related_name/new_data.py: -------------------------------------------------------------------------------- 1 | # This assumes you have the models imported 2 | # or accessible from somewhere allowing you to generate 3 | # these records in your database 4 | 5 | acme = await Organisation.query.create(ident="ACME Ltd") 6 | red_team = await Team.query.create(org=acme, name="Red Team") 7 | blue_team = await Team.query.create(org=acme, name="Blue Team") 8 | green_team = await Team.query.create(org=acme, name="Green Team") 9 | 10 | await Member.query.create(team=red_team, email="charlie@redteam.com") 11 | await Member.query.create(team=red_team, email="brown@redteam.com") 12 | await Member.query.create(team=blue_team, email="snoopy@blueteam.com") 13 | monica = await Member.query.create(team=green_team, email="monica@blueteam.com") 14 | 15 | # New data 16 | user = await User.query.create(member=monica, name="Edgy") 17 | profile = await Profile.query.create(user=user, profile_type="admin") 18 | -------------------------------------------------------------------------------- /docs_src/queries/secrets/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=50) 10 | email: str = edgy.EmailField(max_lengh=100) 11 | password: str = edgy.CharField(max_length=1000, secret=True) 12 | 13 | class Meta: 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/quickstart/esmerald.py: -------------------------------------------------------------------------------- 1 | from esmerald import Esmerald, Gateway, post 2 | 3 | import edgy 4 | from edgy.testclient import DatabaseTestClient as Database 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = edgy.Registry(database=database) 8 | 9 | 10 | class User(edgy.Model): 11 | name: str = edgy.CharField(max_length=100) 12 | email: str = edgy.EmailField(max_length=100) 13 | language: str = edgy.CharField(max_length=200, null=True) 14 | description: str = edgy.TextField(max_length=5000, null=True) 15 | 16 | class Meta: 17 | registry = models 18 | 19 | 20 | @post("/create") 21 | async def create_user(data: User) -> User: 22 | """ 23 | You can perform the same directly like this 24 | as the validations for the model (nulls, mandatories, @field_validators) 25 | already did all the necessary checks defined by you. 26 | """ 27 | user = await data.save() 28 | return user 29 | 30 | 31 | app = models.asgi( 32 | Esmerald( 33 | routes=[Gateway(handler=create_user)], 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /docs_src/quickstart/example1.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | The User model to be created in the database as a table 11 | If no name is provided the in Meta class, it will generate 12 | a "users" table for you. 13 | """ 14 | 15 | is_active: bool = edgy.BooleanField(default=False) 16 | 17 | class Meta: 18 | registry = models 19 | 20 | 21 | # Create the db and tables 22 | # Don't use this in production! Use Alembic or any tool to manage 23 | # The migrations for you 24 | await models.create_all() # noqa 25 | 26 | await User.query.create(is_active=False) # noqa 27 | 28 | user = await User.query.get(id=1) # noqa 29 | print(user) 30 | # User(id=1) 31 | -------------------------------------------------------------------------------- /docs_src/reffk/apis/api_call.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | data = { 4 | "name": "Edgy", 5 | "email": "edgy@esmerald.dev", 6 | "language": "EN", 7 | "description": "A description", 8 | "posts": [ 9 | {"comment": "First comment"}, 10 | {"comment": "Second comment"}, 11 | {"comment": "Third comment"}, 12 | {"comment": "Fourth comment"}, 13 | ], 14 | } 15 | 16 | # Make the API call to create the user with some posts 17 | # This will also create the posts and associate them with the user 18 | # All the posts will be in uppercase as per `field_validator` in the ModelRef. 19 | response = httpx.post("/create", json=data) 20 | -------------------------------------------------------------------------------- /docs_src/reffk/apis/api_call_errors.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | data = { 4 | "name": "Edgy", 5 | "email": "edgy@esmerald.dev", 6 | "language": "EN", 7 | "description": "A description", 8 | } 9 | 10 | # Make the API call to create the user with some posts 11 | # This will also create the posts and associate them with the user 12 | # All the posts will be in uppercase as per `field_validator` in the ModelRef. 13 | response = httpx.post("/create", json=data) 14 | -------------------------------------------------------------------------------- /docs_src/reffk/complex_example.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | from .references import PostRef 7 | 8 | database = Database("sqlite:///db.sqlite") 9 | models = Registry(database=database) 10 | 11 | 12 | class User(edgy.Model): 13 | name: str = edgy.CharField(max_length=255) 14 | posts: List["Post"] = edgy.RefForeignKey(PostRef) 15 | 16 | class Meta: 17 | registry = models 18 | 19 | 20 | class Post(edgy.Model): 21 | user: User = edgy.ForeignKey(User) 22 | comment: str = edgy.TextField() 23 | 24 | class Meta: 25 | registry = models 26 | -------------------------------------------------------------------------------- /docs_src/reffk/example1.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=255) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class Post(edgy.Model): 16 | user: User = edgy.ForeignKey(User) 17 | comment: str = edgy.TextField() 18 | 19 | class Meta: 20 | registry = models 21 | -------------------------------------------------------------------------------- /docs_src/reffk/model_ref/how_to_declare.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import edgy 4 | from edgy import Database, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class Post(edgy.Model): 11 | comment: str = edgy.TextField() 12 | created_at: datetime = edgy.DateTimeField(auto_now_add=True) 13 | 14 | class Meta: 15 | registry = models 16 | -------------------------------------------------------------------------------- /docs_src/reffk/model_ref/model_ref.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from edgy import Database, ModelRef, Registry 4 | 5 | database = Database("sqlite:///db.sqlite") 6 | models = Registry(database=database) 7 | 8 | 9 | class PostRef(ModelRef): 10 | __related_name__ = "posts_set" 11 | comment: str 12 | created_at: datetime 13 | -------------------------------------------------------------------------------- /docs_src/reffk/model_ref/model_ref2.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import edgy 4 | from edgy import Database, ModelRef, Registry 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class Post(edgy.Model): 11 | comment: str = edgy.TextField() 12 | created_at: datetime = edgy.DateTimeField(auto_now_add=True) 13 | 14 | class Meta: 15 | registry = models 16 | 17 | 18 | class PostRef(ModelRef): 19 | __related_name__ = "posts_set" 20 | comment: str 21 | created_at: datetime 22 | -------------------------------------------------------------------------------- /docs_src/reffk/nutshell.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, ModelRef, Registry, run_sync 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class PostRef(ModelRef): 11 | __related_name__ = "posts_set" 12 | comment: str 13 | 14 | 15 | class User(edgy.Model): 16 | name: str = edgy.CharField(max_length=255) 17 | posts: List["Post"] = edgy.RefForeignKey(PostRef) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class Post(edgy.Model): 24 | user: User = edgy.ForeignKey(User) 25 | comment: str = edgy.TextField() 26 | 27 | class Meta: 28 | registry = models 29 | 30 | 31 | # now we do things like 32 | 33 | run_sync( 34 | User.query.create( 35 | PostRef(comment="foo"), 36 | PostRef(comment="bar"), 37 | ), 38 | name="edgy", 39 | posts=[{"comment": "I am a dict"}], 40 | ) 41 | -------------------------------------------------------------------------------- /docs_src/reffk/positional_example.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import edgy 4 | from edgy import Database, Registry, run_sync 5 | 6 | database = Database("sqlite:///db.sqlite") 7 | models = Registry(database=database) 8 | 9 | 10 | class PostRef(ModelRef): 11 | __related_name__ = "posts_set" 12 | comment: str 13 | 14 | 15 | class User(edgy.Model): 16 | name: str = edgy.CharField(max_length=255) 17 | 18 | class Meta: 19 | registry = models 20 | 21 | 22 | class Post(edgy.Model): 23 | user: User = edgy.ForeignKey(User) 24 | comment: str = edgy.TextField() 25 | 26 | class Meta: 27 | registry = models 28 | 29 | 30 | # This time completely without a RefForeignKey 31 | 32 | run_sync( 33 | User.query.create( 34 | PostRef(comment="foo"), 35 | PostRef(comment="bar"), 36 | ) 37 | ) 38 | -------------------------------------------------------------------------------- /docs_src/reffk/references.py: -------------------------------------------------------------------------------- 1 | from edgy import ModelRef 2 | 3 | 4 | class PostRef(ModelRef): 5 | __related_name__ = "posts_set" 6 | comment: str 7 | -------------------------------------------------------------------------------- /docs_src/reflection/autoreflection/datadriven.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.autoreflection import AutoReflectModel 3 | 4 | reflected = edgy.Registry(database="sqlite:///webshopdb.sqlite") 5 | 6 | 7 | class Product(AutoReflectModel): 8 | price = edgy.DecimalField(decimal_places=2) 9 | name = edgy.CharField(max_length=50) 10 | 11 | class Meta: 12 | registry = reflected 13 | template = r"{modelname}_{tablename}" 14 | 15 | 16 | async def main(): 17 | async with reflected: 18 | print( 19 | *[ 20 | product.model_dump() 21 | async for product in reflected.get_model("Product_shoes").query.all() 22 | ] 23 | ) 24 | print( 25 | *[ 26 | product.model_dump() 27 | async for product in reflected.get_model("Product_trousers").query.all() 28 | ] 29 | ) 30 | await reflected.get_model("Product_shoes").query.update( 31 | price=reflected.get_model("Product_shoes").table.c.price + 10 32 | ) 33 | 34 | 35 | edgy.run_sync(main()) 36 | -------------------------------------------------------------------------------- /docs_src/reflection/autoreflection/datadriven_source.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | registry = edgy.Registry(database="sqlite:///webshopdb.sqlite") 4 | 5 | 6 | class Trouser(edgy.Model): 7 | price = edgy.DecimalField(max_digits=2, decimal_places=2) 8 | name = edgy.CharField(max_length=50) 9 | with_pocket = edgy.BooleanField() 10 | size = edgy.IntegerField() 11 | 12 | class Meta: 13 | registry = registry 14 | 15 | 16 | class Shoe(edgy.Model): 17 | price = edgy.DecimalField(decimal_places=2) 18 | name = edgy.CharField(max_length=50) 19 | waterproof = edgy.BooleanField() 20 | size = edgy.FloatField() 21 | 22 | class Meta: 23 | registry = registry 24 | 25 | 26 | async def main(): 27 | async with registry: 28 | await registry.create_all() 29 | await Trouser.query.create(price=10.50, name="Fancy Jeans", with_pocket=True, size=30) 30 | await Shoe.query.create(price=14.50, name="SuperEliteWalk", waterproof=False, size=10.5) 31 | 32 | 33 | edgy.run_sync(main()) 34 | -------------------------------------------------------------------------------- /docs_src/reflection/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | age: int = edgy.IntegerField(minimum=18) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | registry = models 14 | -------------------------------------------------------------------------------- /docs_src/reflection/reflect.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.ReflectModel): 9 | age: int = edgy.IntegerField(minimum=18) 10 | is_active: bool = edgy.BooleanField(default=True) 11 | 12 | class Meta: 13 | tablename = "users" 14 | registry = models 15 | -------------------------------------------------------------------------------- /docs_src/reflection/reflect/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | age: int = edgy.IntegerField(minimum=18, null=True) 10 | is_active: bool = edgy.BooleanField(default=True, null=True) 11 | description: str = edgy.CharField(max_length=255, null=True) 12 | profile_type: str = edgy.CharField(max_length=255, null=True) 13 | username: str = edgy.CharField(max_length=255, null=True) 14 | 15 | class Meta: 16 | registry = models 17 | -------------------------------------------------------------------------------- /docs_src/reflection/reflect/reflect.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class Profile(edgy.ReflectModel): 9 | is_active: bool = edgy.BooleanField(default=True, null=True) 10 | profile_type: str = edgy.CharField(max_length=255, null=True) 11 | username: str = edgy.CharField(max_length=255, null=True) 12 | 13 | class Meta: 14 | tablename = "users" 15 | registry = models 16 | -------------------------------------------------------------------------------- /docs_src/registry/create_schema.py: -------------------------------------------------------------------------------- 1 | from edgy import Database, Registry 2 | 3 | database = Database("") 4 | registry = Registry(database=database) 5 | 6 | 7 | async def create_schema(name: str) -> None: 8 | """ 9 | Creates a new schema in the database. 10 | """ 11 | await registry.schema.create_schema(name, if_not_exists=True) 12 | -------------------------------------------------------------------------------- /docs_src/registry/custom_registry.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | 5 | class MyRegistry(Registry): 6 | """ 7 | Add logic unique to your registry or override 8 | existing functionality. 9 | """ 10 | 11 | ... 12 | 13 | 14 | database = Database("sqlite:///db.sqlite") 15 | models = MyRegistry(database=database) 16 | 17 | 18 | class User(edgy.Model): 19 | """ 20 | The User model to be created in the database as a table 21 | If no name is provided the in Meta class, it will generate 22 | a "users" table for you. 23 | """ 24 | 25 | is_active: bool = edgy.BooleanField(default=False) 26 | 27 | class Meta: 28 | registry = models 29 | -------------------------------------------------------------------------------- /docs_src/registry/default_schema.py: -------------------------------------------------------------------------------- 1 | from edgy import Database, Registry 2 | 3 | database = Database("") 4 | registry = Registry(database=database) 5 | 6 | 7 | async def get_default_schema() -> str: 8 | """ 9 | Returns the default schema name of the given database 10 | """ 11 | await registry.schema.get_default_schema() 12 | -------------------------------------------------------------------------------- /docs_src/registry/drop_schema.py: -------------------------------------------------------------------------------- 1 | from edgy import Database, Registry 2 | 3 | database = Database("") 4 | registry = Registry(database=database) 5 | 6 | 7 | async def drop_schema(name: str) -> None: 8 | """ 9 | Drops a schema from the database. 10 | """ 11 | await registry.schema.drop_schema(name, if_exists=True) 12 | -------------------------------------------------------------------------------- /docs_src/registry/extra/create.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.core.db import fields 3 | from edgy.testclient import DatabaseTestClient as Database 4 | 5 | database = Database("") 6 | alternative = Database("") 7 | models = edgy.Registry(database=database, extra={"alternative": alternative}) 8 | 9 | 10 | class User(edgy.Model): 11 | name: str = fields.CharField(max_length=255) 12 | email: str = fields.CharField(max_length=255) 13 | 14 | class Meta: 15 | registry = models 16 | 17 | 18 | async def bulk_create_users() -> None: 19 | """ 20 | Bulk creates some users. 21 | """ 22 | await User.query.using(database="alternative").bulk_create( 23 | [ 24 | {"name": "Edgy", "email": "edgy@example.com"}, 25 | {"name": "Edgy Alternative", "email": "edgy.alternative@example.com"}, 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /docs_src/registry/extra/declaration.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.core.db import fields 3 | from edgy.testclient import DatabaseTestClient as Database 4 | 5 | database = Database("") 6 | alternative = Database("") 7 | models = edgy.Registry(database=database, extra={"alternative": alternative}) 8 | 9 | 10 | class User(edgy.Model): 11 | name: str = fields.CharField(max_length=255) 12 | email: str = fields.CharField(max_length=255) 13 | 14 | class Meta: 15 | registry = models 16 | -------------------------------------------------------------------------------- /docs_src/registry/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | The User model to be created in the database as a table 11 | If no name is provided the in Meta class, it will generate 12 | a "users" table for you. 13 | """ 14 | 15 | is_active: bool = edgy.BooleanField(default=False) 16 | 17 | class Meta: 18 | registry = models 19 | -------------------------------------------------------------------------------- /docs_src/registry/multiple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | 5 | class MyRegistry(Registry): 6 | """ 7 | Add logic unique to your registry or override 8 | existing functionality. 9 | """ 10 | 11 | ... 12 | 13 | 14 | database = Database("sqlite:///db.sqlite") 15 | models = MyRegistry(database=database) 16 | 17 | 18 | class User(edgy.Model): 19 | is_active: bool = edgy.BooleanField(default=False) 20 | 21 | class Meta: 22 | registry = models 23 | 24 | 25 | another_db = Database("postgressql://user:password@localhost:5432/mydb") 26 | another_registry = MyRegistry(another_db=another_db) 27 | 28 | 29 | class Profile(edgy.Model): 30 | is_active: bool = edgy.BooleanField(default=False) 31 | 32 | class Meta: 33 | registry = another_registry 34 | -------------------------------------------------------------------------------- /docs_src/relationships/embed_parent_with_embedded.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class Address(edgy.Model): 9 | street = edgy.CharField(max_length=100) 10 | city = edgy.CharField(max_length=100) 11 | 12 | class Meta: 13 | # we don't want a table just a template 14 | abstract = True 15 | 16 | 17 | class User(edgy.Model): 18 | is_active: bool = edgy.BooleanField(default=True) 19 | 20 | class Meta: 21 | registry = models 22 | 23 | 24 | class Profile(edgy.Model): 25 | name: str = edgy.CharField(max_length=100) 26 | user: User = edgy.OneToOne("User", on_delete=edgy.CASCADE, embed_parent=("address", "profile")) 27 | address: Address = Address 28 | 29 | class Meta: 30 | registry = models 31 | 32 | 33 | user = edgy.run_sync(User.query.create()) 34 | edgy.run_sync( 35 | Profile.query.create( 36 | name="edgy", user=user, address={"street": "Rainbowstreet 123", "city": "London"} 37 | ) 38 | ) 39 | # use the reverse link 40 | address = edgy.run_sync(user.profile.get()) 41 | # access the profile 42 | address.profile 43 | -------------------------------------------------------------------------------- /docs_src/relationships/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=True) 10 | first_name: str = edgy.CharField(max_length=50, null=True) 11 | last_name: str = edgy.CharField(max_length=50, null=True) 12 | email: str = edgy.EmailField(max_lengh=100) 13 | password: str = edgy.CharField(max_length=1000, null=True) 14 | 15 | class Meta: 16 | registry = models 17 | 18 | 19 | class Profile(edgy.Model): 20 | user: User = edgy.ForeignKey(User, on_delete=edgy.CASCADE) 21 | 22 | class Meta: 23 | registry = models 24 | -------------------------------------------------------------------------------- /docs_src/relationships/multiple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=True) 10 | first_name: str = edgy.CharField(max_length=50) 11 | last_name: str = edgy.CharField(max_length=50) 12 | email: str = edgy.EmailField(max_lengh=100) 13 | password: str = edgy.CharField(max_length=1000) 14 | 15 | class Meta: 16 | registry = models 17 | 18 | 19 | class Thread(edgy.Model): 20 | sender: User = edgy.ForeignKey( 21 | User, 22 | on_delete=edgy.CASCADE, 23 | related_name="sender", 24 | ) 25 | receiver: User = edgy.ForeignKey( 26 | User, 27 | on_delete=edgy.CASCADE, 28 | related_name="receiver", 29 | ) 30 | message: str = edgy.TextField() 31 | 32 | class Meta: 33 | registry = models 34 | -------------------------------------------------------------------------------- /docs_src/relationships/onetoone.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=True) 10 | first_name: str = edgy.CharField(max_length=50) 11 | last_name: str = edgy.CharField(max_length=50) 12 | email: str = edgy.EmailField(max_lengh=100) 13 | password: str = edgy.CharField(max_length=1000) 14 | 15 | class Meta: 16 | registry = models 17 | 18 | 19 | class Profile(edgy.Model): 20 | user: User = edgy.OneToOneField(User, on_delete=edgy.CASCADE) 21 | 22 | class Meta: 23 | registry = models 24 | -------------------------------------------------------------------------------- /docs_src/settings/custom_settings.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from edgy import EdgySettings 4 | from esmerald.conf.enums import EnvironmentType 5 | 6 | 7 | class MyCustomSettings(EdgySettings): 8 | """ 9 | My settings overriding default values and add new ones. 10 | """ 11 | 12 | environment: Optional[str] = EnvironmentType.TESTING 13 | 14 | # new settings 15 | my_new_setting: str = "A text" 16 | -------------------------------------------------------------------------------- /docs_src/signals/custom.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.core import signals 3 | from edgy import Signal 4 | # or: 5 | # from blinker import Signal 6 | 7 | database = edgy.Database("sqlite:///db.sqlite") 8 | registry = edgy.Registry(database=database) 9 | 10 | 11 | class User(edgy.Model): 12 | id: int = edgy.BigIntegerField(primary_key=True) 13 | name: str = edgy.CharField(max_length=255) 14 | email: str = edgy.CharField(max_length=255) 15 | 16 | class Meta: 17 | registry = registry 18 | 19 | 20 | # Create the custom signal 21 | User.meta.signals.on_verify = Signal() 22 | -------------------------------------------------------------------------------- /docs_src/signals/disconnect.py: -------------------------------------------------------------------------------- 1 | async def trigger_notifications(sender, instance, **kwargs): 2 | """ 3 | Sends email and push notification 4 | """ 5 | send_email(instance.email) 6 | send_push_notification(instance.email) 7 | 8 | 9 | # Disconnect the given function 10 | User.meta.signals.on_verify.disconnect(trigger_notifications) 11 | -------------------------------------------------------------------------------- /docs_src/signals/logic.py: -------------------------------------------------------------------------------- 1 | async def create_user(**kwargs): 2 | """ 3 | Creates a user 4 | """ 5 | await User.query.create(**kwargs) 6 | 7 | 8 | async def is_verified_user(id: int): 9 | """ 10 | Checks if user is verified and sends notification 11 | if true. 12 | """ 13 | user = await User.query.get(pk=id) 14 | 15 | if user.is_verified: 16 | # triggers the custom signal 17 | await User.meta.signals.on_verify.send_async(User, instance=user) 18 | # or when maybe a proxy 19 | await User.meta.signals.on_verify.send_async(User.get_real_class(), instance=user) 20 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/disconnect.py: -------------------------------------------------------------------------------- 1 | from edgy.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save.connect_via(User) 12 | async def after_creation(sender, instance, **kwargs): 13 | """ 14 | Sends a notification to the user 15 | """ 16 | send_notification(instance.email) 17 | 18 | 19 | # Disconnect the given function 20 | User.meta.signals.post_save.disconnect(after_creation) 21 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | database = edgy.Database("sqlite:///db.sqlite") 4 | registry = edgy.Registry(database=database) 5 | 6 | 7 | class User(edgy.Model): 8 | id: int = edgy.BigIntegerField(primary_key=True) 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.CharField(max_length=255) 11 | is_verified: bool = edgy.BooleanField(default=False) 12 | 13 | class Meta: 14 | registry = registry 15 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/multiple.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | database = edgy.Database("sqlite:///db.sqlite") 4 | registry = edgy.Registry(database=database) 5 | 6 | 7 | class User(edgy.Model): 8 | id: int = edgy.BigIntegerField(primary_key=True) 9 | name: str = edgy.CharField(max_length=255) 10 | email: str = edgy.CharField(max_length=255) 11 | 12 | class Meta: 13 | registry = registry 14 | 15 | 16 | class Profile(edgy.Model): 17 | id: int = edgy.BigIntegerField(primary_key=True) 18 | profile_type: str = edgy.CharField(max_length=255) 19 | 20 | class Meta: 21 | registry = registry 22 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/multiple_receivers.py: -------------------------------------------------------------------------------- 1 | from edgy.core.signals import post_save 2 | 3 | 4 | def push_notification(email: str) -> None: 5 | # Sends a push notification 6 | ... 7 | 8 | 9 | def send_email(email: str) -> None: 10 | # Sends an email 11 | ... 12 | 13 | 14 | @post_save.connect_via(User) 15 | async def after_creation(sender, instance, **kwargs): 16 | """ 17 | Sends a notification to the user 18 | """ 19 | send_email(instance.email) 20 | 21 | 22 | @post_save.connect_via(User) 23 | async def do_something_else(sender, instance, **kwargs): 24 | """ 25 | Sends a notification to the user 26 | """ 27 | push_notification(instance.email) 28 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/post_multiple.py: -------------------------------------------------------------------------------- 1 | from edgy.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save.connect_via(User) 12 | @post_save.connect_via(Profile) 13 | async def after_creation(sender, instance, **kwargs): 14 | """ 15 | Sends a notification to the user 16 | """ 17 | if isinstance(instance, User): 18 | send_notification(instance.email) 19 | else: 20 | # something else for Profile 21 | ... 22 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/post_save.py: -------------------------------------------------------------------------------- 1 | from edgy.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save.connect_via(User) 12 | async def after_creation(sender, instance, **kwargs): 13 | """ 14 | Sends a notification to the user 15 | """ 16 | send_notification(instance.email) 17 | -------------------------------------------------------------------------------- /docs_src/signals/register.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | from edgy import Signal 4 | # or: 5 | # from blinker import Signal 6 | 7 | database = edgy.Database("sqlite:///db.sqlite") 8 | registry = edgy.Registry(database=database) 9 | 10 | 11 | class User(edgy.Model): 12 | id: int = edgy.BigIntegerField(primary_key=True) 13 | name: str = edgy.CharField(max_length=255) 14 | email: str = edgy.CharField(max_length=255) 15 | 16 | class Meta: 17 | registry = registry 18 | 19 | 20 | # Create the custom signal 21 | User.meta.signals.on_verify = Signal() 22 | 23 | 24 | # Create the receiver 25 | async def trigger_notifications(sender, instance, **kwargs): 26 | """ 27 | Sends email and push notification 28 | """ 29 | send_email(instance.email) 30 | send_push_notification(instance.email) 31 | 32 | 33 | # Register the receiver into the new Signal. 34 | User.meta.signals.on_verify.connect(trigger_notifications) 35 | -------------------------------------------------------------------------------- /docs_src/signals/rewire.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | from edgy import Signal 4 | # or: 5 | # from blinker import Signal 6 | 7 | database = edgy.Database("sqlite:///db.sqlite") 8 | registry = edgy.Registry(database=database) 9 | 10 | 11 | class User(edgy.Model): 12 | id: int = edgy.BigIntegerField(primary_key=True) 13 | name: str = edgy.CharField(max_length=255) 14 | email: str = edgy.CharField(max_length=255) 15 | 16 | class Meta: 17 | registry = registry 18 | 19 | 20 | # Overwrite a model lifecycle Signal; this way the main signals.pre_delete is not triggered 21 | User.meta.signals.pre_delete = Signal() 22 | 23 | # Update all lifecyle signals. Replace pre_delete again with the default 24 | User.meta.signals.set_lifecycle_signals_from(signals) 25 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/domain_mixin.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.multi_tenancy.models import DomainMixin, TenantMixin 3 | 4 | database = edgy.Database("") 5 | registry = edgy.Registry(database=database) 6 | 7 | 8 | class Tenant(TenantMixin): 9 | """ 10 | Inherits all the fields from the `TenantMixin`. 11 | """ 12 | 13 | class Meta: 14 | registry = registry 15 | 16 | 17 | class Domain(DomainMixin): 18 | """ 19 | Inherits all the fields from the `DomainMixin`. 20 | """ 21 | 22 | class Meta: 23 | registry = registry 24 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/example/api.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from esmerald import get 4 | from myapp.models import Product 5 | 6 | import edgy 7 | 8 | 9 | @get("/products") 10 | async def products() -> List[Product]: 11 | """ 12 | Returns the products associated to a tenant or 13 | all the "shared" products if tenant is None. 14 | 15 | The tenant was set in the `TenantMiddleware` which 16 | means that there is no need to use the `using` anymore. 17 | """ 18 | products = await Product.query.all() 19 | return products 20 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/example/app.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from esmerald import Esmerald, Gateway, get 4 | from myapp.middleware import TenantMiddleware 5 | from myapp.models import Product 6 | 7 | import edgy 8 | 9 | database = edgy.Database("") 10 | models = edgy.Registry(database=database) 11 | 12 | 13 | @get("/products") 14 | async def products() -> List[Product]: 15 | """ 16 | Returns the products associated to a tenant or 17 | all the "shared" products if tenant is None. 18 | 19 | The tenant was set in the `TenantMiddleware` which 20 | means that there is no need to use the `using` anymore. 21 | """ 22 | products = await Product.query.all() 23 | return products 24 | 25 | 26 | app = models.asgi( 27 | Esmerald( 28 | routes=[Gateway(handler=products)], 29 | middleware=[TenantMiddleware], 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/example/queries.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | # Query the products for the `Edgy` user from the `edgy` schema 4 | # by passing the tenant and email header. 5 | async with httpx.AsyncClient() as client: 6 | response = await client.get( 7 | "/products", headers={"tenant": "edgy", "email": "edgy@esmerald.dev"} 8 | ) 9 | assert response.status_code == 200 10 | assert len(response.json()) == 10 # total inserted in the `edgy` schema. 11 | 12 | # Query the shared database, so no tenant or email associated 13 | # In the headers. 14 | async with httpx.AsyncClient() as client: 15 | response = await client.get("/products") 16 | assert response.status_code == 200 17 | assert len(response.json()) == 25 # total inserted in the `shared` database. 18 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/example/settings.py: -------------------------------------------------------------------------------- 1 | from edgy.contrib.multi_tenancy.settings import TenancySettings 2 | 3 | 4 | class EdgySettings(TenancySettings): 5 | tenant_model: str = "Tenant" 6 | """ 7 | The Tenant model created 8 | """ 9 | auth_user_model: str = "User" 10 | """ 11 | The `user` table created. Not the `HubUser`! 12 | """ 13 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/tenant_mixin.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.multi_tenancy.models import TenantMixin 3 | 4 | database = edgy.Database("") 5 | registry = edgy.Registry(database=database) 6 | 7 | 8 | class Tenant(TenantMixin): 9 | """ 10 | Inherits all the fields from the `TenantMixin`. 11 | """ 12 | 13 | class Meta: 14 | registry = registry 15 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/tenant_model.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.multi_tenancy import TenantModel 3 | 4 | database = edgy.Database("") 5 | registry = edgy.Registry(database=database) 6 | 7 | 8 | class User(TenantModel): 9 | """ 10 | A `users` table that should be created in the `shared` schema 11 | (or public) and in the subsequent new schemas. 12 | """ 13 | 14 | name: str = edgy.CharField(max_length=255) 15 | email: str = edgy.CharField(max_length=255) 16 | 17 | class Meta: 18 | registry = registry 19 | is_tenant = True 20 | -------------------------------------------------------------------------------- /docs_src/tenancy/contrib/tenant_user_mixin.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.contrib.multi_tenancy.models import DomainMixin, TenantMixin, TenantUserMixin 3 | 4 | database = edgy.Database("") 5 | registry = edgy.Registry(database=database) 6 | 7 | 8 | class Tenant(TenantMixin): 9 | """ 10 | Inherits all the fields from the `TenantMixin`. 11 | """ 12 | 13 | class Meta: 14 | registry = registry 15 | 16 | 17 | class Domain(DomainMixin): 18 | """ 19 | Inherits all the fields from the `DomainMixin`. 20 | """ 21 | 22 | class Meta: 23 | registry = registry 24 | 25 | 26 | class TenantUser(TenantUserMixin): 27 | """ 28 | Inherits all the fields from the `TenantUserMixin`. 29 | """ 30 | 31 | class Meta: 32 | registry = registry 33 | -------------------------------------------------------------------------------- /docs_src/tenancy/example/api.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from esmerald import Esmerald, Gateway, get 4 | 5 | import edgy 6 | 7 | database = edgy.Database("") 8 | models = edgy.Registry(database=database) 9 | 10 | 11 | @get("/products") 12 | async def products() -> List[Product]: 13 | """ 14 | Returns the products associated to a tenant or 15 | all the "shared" products if tenant is None. 16 | """ 17 | products = await Product.query.all() 18 | return products 19 | 20 | 21 | app = models.asgi( 22 | Esmerald( 23 | routes=[Gateway(handler=products)], 24 | middleware=[TenantMiddleware], 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /docs_src/tenancy/example/data.py: -------------------------------------------------------------------------------- 1 | async def create_data(): 2 | """ 3 | Creates mock data. 4 | """ 5 | # Create some users in the main users table 6 | esmerald = await User.query.create(name="esmerald") 7 | 8 | # Create a tenant for Edgy (only) 9 | tenant = await Tenant.query.create( 10 | schema_name="edgy", 11 | tenant_name="edgy", 12 | ) 13 | 14 | # Create a user in the `User` table inside the `edgy` tenant. 15 | edgy = await User.query.using(schema=tenant.schema_name).create( 16 | name="Edgy schema user", 17 | ) 18 | 19 | # Products for Edgy (inside edgy schema) 20 | for i in range(10): 21 | await Product.query.using(schema=tenant.schema_name).create( 22 | name=f"Product-{i}", 23 | user=edgy, 24 | ) 25 | 26 | # Products for Esmerald (no schema associated, defaulting to the public schema or "shared") 27 | for i in range(25): 28 | await Product.query.create(name=f"Product-{i}", user=esmerald) 29 | -------------------------------------------------------------------------------- /docs_src/tenancy/example/query.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | 4 | async def query(): 5 | response = await httpx.get("/products", headers={"tenant": "edgy"}) 6 | 7 | # Total products created for `edgy` schema 8 | assert len(response.json()) == 10 9 | 10 | # Response for the "shared", no tenant associated. 11 | response = await httpx.get("/products") 12 | assert len(response.json()) == 25 13 | -------------------------------------------------------------------------------- /docs_src/tenancy/using/schemas.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | is_active: bool = edgy.BooleanField(default=False) 10 | 11 | class Meta: 12 | registry = models 13 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_basic.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class User(edgy.Model): 8 | name: str = edgy.CharField(max_length=100, null=True) 9 | language: str = edgy.CharField(max_length=200, null=True) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class UserFactory(ModelFactory): 16 | class Meta: 17 | model = User 18 | 19 | language = FactoryField(callback="language_code") 20 | 21 | 22 | user_factory = UserFactory(language="eng") 23 | 24 | user_model_instance = user_factory.build() 25 | # provide the name edgy 26 | user_model_instance_with_name_edgy = user_factory.build(overwrites={"name": "edgy"}) 27 | # with saving 28 | user_model_instance = user_factory.build(save=True) 29 | # or the async variant 30 | user_model_instance = edgy.run_sync(user_factory.build_and_save()) 31 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_exclude.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.exceptions import ExcludeValue 3 | from edgy.testing.factory import ModelFactory, FactoryField 4 | 5 | test_database = DatabaseTestClient(...) 6 | models = edgy.Registry(database=...) 7 | 8 | 9 | def callback(field_instance, context, parameters): 10 | raise ExcludeValue 11 | 12 | 13 | class User(edgy.Model): 14 | name: str = edgy.CharField(max_length=100, null=True) 15 | language: str = edgy.CharField(max_length=200, null=True) 16 | 17 | class Meta: 18 | registry = models 19 | 20 | 21 | class UserFactory(ModelFactory): 22 | class Meta: 23 | model = User 24 | 25 | language = FactoryField(callback=callback) 26 | 27 | 28 | user_factory = UserFactory() 29 | 30 | user_model_instance = user_factory.build(exclude={"name"}) 31 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_field_overwrite.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | test_database = DatabaseTestClient(...) 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.Model): 9 | password = edgy.fields.CharField(max_length=100) 10 | icon = edgy.fields.ImageField() 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class UserFactory(ModelFactory): 17 | class Meta: 18 | model = User 19 | 20 | password = FactoryField(field_type="PasswordField") 21 | icon = FactoryField(field_type=edgy.fields.FileField) 22 | 23 | 24 | user_factory = UserFactory() 25 | 26 | # now the password uses the password field default mappings and for ImageField the FileField defaults 27 | user_model = user_factory.build() 28 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_fields_exclude.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | test_database = DatabaseTestClient(...) 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=100, null=True) 10 | language: str = edgy.CharField(max_length=200, null=True) 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class UserFactory(ModelFactory): 17 | class Meta: 18 | model = User 19 | 20 | language = FactoryField(callback="language_code") 21 | # remove the name field 22 | no_name = FactoryField(exclude=True, name="name") 23 | 24 | 25 | user_factory = UserFactory() 26 | 27 | user_model_instance = user_factory.build() 28 | # you can however provide it explicit 29 | user_model_instance_with_name = UserFactory(name="edgy").build() 30 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_fields_exclude_autoincrement.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | test_database = DatabaseTestClient(...) 5 | models = edgy.Registry(database=...) 6 | 7 | 8 | class User(edgy.Model): 9 | name: str = edgy.CharField(max_length=100, null=True) 10 | language: str = edgy.CharField(max_length=200, null=True) 11 | 12 | class Meta: 13 | registry = models 14 | 15 | 16 | class UserFactory(ModelFactory): 17 | class Meta: 18 | model = User 19 | 20 | language = FactoryField(callback="language_code") 21 | exclude_autoincrement = False 22 | 23 | 24 | user_factory = UserFactory() 25 | 26 | user_model = user_factory.build() 27 | # now the id field is stubbed 28 | assert hasattr(user_model, "id") 29 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_mapping.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory.mappings import DEFAULT_MAPPING 3 | from edgy.testing.factory import ModelFactory, FactoryField 4 | 5 | test_database = DatabaseTestClient(...) 6 | models = edgy.Registry(database=...) 7 | 8 | 9 | class User(edgy.Model): 10 | password = edgy.fields.PasswordField(max_length=100) 11 | icon = edgy.fields.ImageField() 12 | 13 | class Meta: 14 | registry = models 15 | 16 | 17 | class UserFactory(ModelFactory): 18 | class Meta: 19 | model = User 20 | mappings = {"ImageField": DEFAULT_MAPPING["FileField"], "PasswordField": None} 21 | 22 | 23 | class UserSubFactory(UserFactory): 24 | class Meta: 25 | model = User 26 | 27 | 28 | user_factory = UserFactory() 29 | 30 | # now the password is excluded and for ImageField the FileField defaults are used 31 | user_model = user_factory.build() 32 | 33 | # this is inherited to children 34 | user_model = UserSubFactory().build() 35 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_mapping2.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory.mappings import DEFAULT_MAPPING 3 | from edgy.testing.factory import ModelFactory, FactoryField 4 | 5 | test_database = DatabaseTestClient(...) 6 | models = edgy.Registry(database=...) 7 | 8 | 9 | # the true user password simulator 10 | def PasswordField_callback(field: FactoryField, context, parameters: dict[str, Any]) -> Any: 11 | return context["faker"].random_element(["company", "password123", "querty", "asdfg"]) 12 | 13 | 14 | class User(edgy.Model): 15 | password = edgy.fields.PasswordField(max_length=100) 16 | 17 | class Meta: 18 | registry = models 19 | 20 | 21 | class UserFactory(ModelFactory): 22 | class Meta: 23 | model = User 24 | mappings = {"PasswordField": PasswordField_callback} 25 | 26 | 27 | user_factory = UserFactory() 28 | 29 | # now PasswordFields use a special custom mapping which provides common user passwords 30 | user_model = user_factory.build() 31 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_save.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class User(edgy.Model): 8 | name: str = edgy.CharField(max_length=100, null=True) 9 | language: str = edgy.CharField(max_length=200, null=True) 10 | 11 | class Meta: 12 | registry = models 13 | 14 | 15 | class UserFactory(ModelFactory): 16 | class Meta: 17 | model = User 18 | 19 | language = FactoryField(callback="language_code") 20 | 21 | 22 | user_factory = UserFactory(language="eng") 23 | 24 | user_model_instance = await user_factory.build_and_save() 25 | 26 | # or sync 27 | user_model_instance = user_factory.build(save=True) 28 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_to_field.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class Group(edgy.Model): 8 | name: str = edgy.CharField(max_length=100, null=True) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class User(edgy.Model): 15 | name: str = edgy.CharField(max_length=100, null=True) 16 | language: str = edgy.CharField(max_length=200, null=True) 17 | group = edgy.ForeignKey(Group) 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class GroupFactory(ModelFactory): 24 | class Meta: 25 | model = Group 26 | 27 | 28 | class UserFactory(ModelFactory): 29 | class Meta: 30 | model = User 31 | 32 | language = FactoryField(callback="language_code") 33 | group = GroupFactory().to_factory_field() 34 | 35 | 36 | user_factory = UserFactory(language="eng") 37 | 38 | user_model_instance = user_factory.build() 39 | -------------------------------------------------------------------------------- /docs_src/testing/factory/factory_to_fields.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class Group(edgy.Model): 8 | name: str = edgy.CharField(max_length=100, null=True) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class User(edgy.Model): 15 | name: str = edgy.CharField(max_length=100, null=True) 16 | language: str = edgy.CharField(max_length=200, null=True) 17 | group = edgy.ForeignKey(Group, related_name="users") 18 | 19 | class Meta: 20 | registry = models 21 | 22 | 23 | class UserFactory(ModelFactory): 24 | class Meta: 25 | model = User 26 | 27 | language = FactoryField(callback="language_code") 28 | 29 | 30 | class GroupFactory(ModelFactory): 31 | class Meta: 32 | model = Group 33 | 34 | users = UserFactory().to_factory_field() 35 | 36 | 37 | group_factory = GroupFactory() 38 | 39 | group_factory_instance = group_factory.build() 40 | -------------------------------------------------------------------------------- /docs_src/testing/factory/sequences.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class User(edgy.Model): 8 | name: str = edgy.CharField(max_length=100) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class UserFactory(ModelFactory): 15 | class Meta: 16 | model = User 17 | 18 | name = FactoryField( 19 | callback=lambda field, context, parameters: f"user-{field.get_callcount()}" 20 | ) 21 | 22 | 23 | user_model_instance = UserFactory().build() 24 | assert user_model_instance.name == "user-1" 25 | user_model_instance = UserFactory().build() 26 | assert user_model_instance.name == "user-2" 27 | # reset 28 | UserFactory.meta.callcounts.clear() 29 | 30 | user_model_instance = UserFactory().build() 31 | assert user_model_instance.name == "user-1" 32 | 33 | # provide a different callcounts dict 34 | user_model_instance = UserFactory().build(callcounts={}) 35 | assert user_model_instance.name == "user-1" 36 | -------------------------------------------------------------------------------- /docs_src/testing/factory/sequences_even.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class User(edgy.Model): 8 | name: str = edgy.CharField(max_length=100) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class UserFactory(ModelFactory): 15 | class Meta: 16 | model = User 17 | 18 | name = FactoryField( 19 | callback=lambda field, context, parameters: f"user-{field.inc_callcount()}" 20 | ) 21 | 22 | 23 | user_model_instance = UserFactory().build() 24 | assert user_model_instance.name == "user-2" 25 | user_model_instance = UserFactory().build() 26 | assert user_model_instance.name == "user-4" 27 | # reset 28 | UserFactory.meta.callcounts.clear() 29 | 30 | user_model_instance = UserFactory().build() 31 | assert user_model_instance.name == "user-2" 32 | 33 | # provide a different callcounts dict 34 | user_model_instance = UserFactory().build(callcounts={}) 35 | assert user_model_instance.name == "user-2" 36 | -------------------------------------------------------------------------------- /docs_src/testing/factory/sequences_odd.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy.testing.factory import ModelFactory, FactoryField 3 | 4 | models = edgy.Registry(database=...) 5 | 6 | 7 | class User(edgy.Model): 8 | name: str = edgy.CharField(max_length=100) 9 | 10 | class Meta: 11 | registry = models 12 | 13 | 14 | class UserFactory(ModelFactory): 15 | class Meta: 16 | model = User 17 | 18 | name = FactoryField( 19 | callback=lambda field, context, parameters: f"user-{field.inc_callcount()}" 20 | ) 21 | 22 | 23 | # manipulate the callcounter. Requires callcounts argument as no context is available here 24 | UserFactory.meta.fields["name"].inc_callcount(amount=-1, callcounts=UserFactory.meta.callcounts) 25 | user_model_instance = UserFactory().build() 26 | assert user_model_instance.name == "user-1" 27 | user_model_instance = UserFactory().build() 28 | assert user_model_instance.name == "user-3" 29 | -------------------------------------------------------------------------------- /docs_src/tips/lru.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | 4 | @lru_cache() 5 | def get_db_connection(): 6 | # Encapsulate to delay the import 7 | from esmerald.conf import settings 8 | 9 | return settings.db_connection 10 | -------------------------------------------------------------------------------- /docs_src/transactions/context_manager.py: -------------------------------------------------------------------------------- 1 | from esmerald import Request, post 2 | from models import Profile, User 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from edgy import Database, Registry 6 | 7 | # These settings should be placed somewhere 8 | # Central where it can be accessed anywhere. 9 | database = Database("sqlite:///db.sqlite") 10 | models = Registry(database=database) 11 | 12 | 13 | class UserIn(BaseModel): 14 | email: EmailStr 15 | 16 | 17 | @post("/create", description="Creates a user and associates to a profile.") 18 | async def create_user(data: UserIn, request: Request) -> None: 19 | # This database insert occurs within a transaction. 20 | # It will be rolled back by the `RuntimeError`. 21 | 22 | async with User.transaction(): 23 | user = await User.query.create(email=data.email, is_active=True) 24 | await Profile.query.create(user=user) 25 | raise RuntimeError() 26 | -------------------------------------------------------------------------------- /docs_src/transactions/context_manager2.py: -------------------------------------------------------------------------------- 1 | from esmerald import Request, post 2 | from models import Profile, User 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from edgy import Database, Registry 6 | 7 | # These settings should be placed somewhere 8 | # Central where it can be accessed anywhere. 9 | models = Registry(database="sqlite:///db.sqlite", extra={"another": "sqlite:///another.sqlite"}) 10 | 11 | 12 | class UserIn(BaseModel): 13 | email: EmailStr 14 | 15 | 16 | @post("/create", description="Creates a user and associates to a profile.") 17 | async def create_user(data: UserIn, request: Request) -> None: 18 | # This database insert occurs within a transaction. 19 | # It will be rolled back by force_rollback. 20 | 21 | queryset = User.query.using(database="another") 22 | 23 | async with queryset.transaction(force_rollback=True): 24 | user = await queryset.create(email=data.email, is_active=True) 25 | await Profile.query.using(database="another").create(user=user) 26 | -------------------------------------------------------------------------------- /docs_src/transactions/context_manager_direct.py: -------------------------------------------------------------------------------- 1 | from esmerald import Request, post 2 | from models import Profile, User 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from edgy import Database, Registry 6 | 7 | # These settings should be placed somewhere 8 | # Central where it can be accessed anywhere. 9 | models = Registry(database="sqlite:///db.sqlite", extra={"another": "sqlite:///another.sqlite"}) 10 | 11 | 12 | class UserIn(BaseModel): 13 | email: EmailStr 14 | 15 | 16 | @post("/create", description="Creates a user and associates to a profile.") 17 | async def create_user(data: UserIn, request: Request) -> None: 18 | # This database insert occurs within a transaction. 19 | # It will be rolled back by a transaction with force_rollback active. 20 | 21 | queryset = User.query.using(database="another") 22 | 23 | async with queryset.database as database, database.transaction(force_rollback=True): 24 | user = await queryset.create(email=data.email, is_active=True) 25 | await Profile.query.using(database="another").create(user=user) 26 | -------------------------------------------------------------------------------- /docs_src/transactions/decorator.py: -------------------------------------------------------------------------------- 1 | from esmerald import Request, post 2 | from models import Profile, User 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from edgy import Database, Registry 6 | 7 | # These settings should be placed somewhere 8 | # Central where it can be accessed anywhere. 9 | models = Registry(database="sqlite:///db.sqlite") 10 | 11 | 12 | class UserIn(BaseModel): 13 | email: EmailStr 14 | 15 | 16 | @post("/create", description="Creates a user and associates to a profile.") 17 | @models.database.transaction() 18 | async def create_user(data: UserIn, request: Request) -> None: 19 | # This database insert occurs within a transaction. 20 | # It will be rolled back by the `RuntimeError`. 21 | 22 | user = await User.query.create(email=data.email, is_active=True) 23 | await Profile.query.create(user=user) 24 | raise RuntimeError() 25 | -------------------------------------------------------------------------------- /docs_src/transactions/models.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | from edgy import Database, Registry 3 | 4 | database = Database("sqlite:///db.sqlite") 5 | models = Registry(database=database) 6 | 7 | 8 | class User(edgy.Model): 9 | """ 10 | The User model to be created in the database as a table 11 | If no name is provided the in Meta class, it will generate 12 | a "users" table for you. 13 | """ 14 | 15 | email: str = edgy.EmailField(unique=True, max_length=120) 16 | is_active: bool = edgy.BooleanField(default=False) 17 | 18 | class Meta: 19 | registry = models 20 | 21 | 22 | class Profile(edgy.Model): 23 | user: User = edgy.ForeignKey(User, on_delete=edgy.CASCADE) 24 | 25 | class Meta: 26 | registry = models 27 | -------------------------------------------------------------------------------- /edgy/__main__.py: -------------------------------------------------------------------------------- 1 | from edgy.cli.cli import edgy_cli 2 | 3 | 4 | def run_cli() -> None: 5 | edgy_cli() 6 | 7 | 8 | if __name__ == "__main__": # pragma: no cover 9 | run_cli() 10 | -------------------------------------------------------------------------------- /edgy/cli/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Migrate, alembic_version 2 | 3 | __all__ = ["alembic_version", "Migrate"] 4 | -------------------------------------------------------------------------------- /edgy/cli/constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_TEMPLATE_NAME = "default" 2 | APP_PARAMETER = "--app" 3 | DISCOVERY_PRELOADS = ["application", "app", "main", "asgi"] 4 | COMMANDS_WITHOUT_APP = ["list-templates", "inspectdb", "init"] 5 | -------------------------------------------------------------------------------- /edgy/cli/exceptions.py: -------------------------------------------------------------------------------- 1 | from edgy.exceptions import EdgyException 2 | 3 | 4 | class MissingParameterException(EdgyException): ... 5 | -------------------------------------------------------------------------------- /edgy/cli/operations/__init__.py: -------------------------------------------------------------------------------- 1 | from .branches import branches as branches # noqa 2 | from .check import check as check # noqa 3 | from .current import current as current # noqa 4 | from .downgrade import downgrade as downgrade # noqa 5 | from .edit import edit as edit # noqa 6 | from .heads import heads as heads # noqa 7 | from .history import history as history # noqa 8 | from .init import init as init # noqa 9 | from .inspectdb import inspect_db as inspect_db # noqa 10 | from .list_templates import list_templates as list_templates # noqa 11 | from .makemigrations import makemigrations as makemigrations # noqa 12 | from .merge import merge as merge # noqa 13 | from .migrate import migrate as migrate # noqa 14 | from .revision import revision as revision # noqa 15 | from .shell import shell as shell # noqa 16 | from .show import show as show # noqa 17 | from .stamp import stamp as stamp # noqa 18 | from .admin_serve import admin_serve as admin_serve # noqa 19 | -------------------------------------------------------------------------------- /edgy/cli/operations/branches.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client to interact with Edgy models and migrations. 3 | """ 4 | 5 | import click 6 | 7 | from edgy.cli.base import branches as _branches 8 | from edgy.cli.decorators import add_migration_directory_option 9 | 10 | 11 | @add_migration_directory_option 12 | @click.option("-v", "--verbose", is_flag=True, help="Use more verbose output") 13 | @click.command() 14 | def branches(verbose: bool) -> None: 15 | """Show current branch points""" 16 | _branches(verbose) 17 | -------------------------------------------------------------------------------- /edgy/cli/operations/check.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import check as _check 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.command() 9 | def check() -> None: 10 | """Check if there are any new operations to migrate""" 11 | _check() 12 | -------------------------------------------------------------------------------- /edgy/cli/operations/current.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import current as _current 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.option("-v", "--verbose", is_flag=True, help="Use more verbose output") 9 | @click.command() 10 | def current(verbose: bool) -> None: 11 | """Display the current revision for each database.""" 12 | _current(verbose) 13 | -------------------------------------------------------------------------------- /edgy/cli/operations/downgrade.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import click 4 | 5 | from edgy.cli.base import downgrade as _downgrade 6 | from edgy.cli.decorators import add_migration_directory_option 7 | 8 | 9 | @add_migration_directory_option 10 | @click.option( 11 | "--sql", is_flag=True, help=("Don't emit SQL to database - dump to standard output instead") 12 | ) 13 | @click.option( 14 | "--tag", default=None, help=('Arbitrary "tag" name - can be used by custom env.py scripts') 15 | ) 16 | @click.option( 17 | "-x", "--arg", multiple=True, help="Additional arguments consumed by custom env.py scripts" 18 | ) 19 | @click.command(context_settings={"ignore_unknown_options": True}) 20 | @click.argument("revision", default="-1") 21 | def downgrade(sql: bool, tag: str, arg: Any, revision: str) -> None: 22 | """Revert to a previous version""" 23 | _downgrade(revision, sql, tag, arg) 24 | -------------------------------------------------------------------------------- /edgy/cli/operations/edit.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import edit as _edit 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.command() 9 | @click.argument("revision", default="head") 10 | def edit(revision: str) -> None: 11 | """Edit a revision file""" 12 | _edit(revision) 13 | -------------------------------------------------------------------------------- /edgy/cli/operations/heads.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import heads as _heads 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.option("-v", "--verbose", is_flag=True, help="Use more verbose output") 9 | @click.option( 10 | "--resolve-dependencies", is_flag=True, help="Treat dependency versions as down revisions" 11 | ) 12 | @click.command() 13 | def heads(verbose: bool, resolve_dependencies: bool) -> None: 14 | """Show current available heads in the script directory""" 15 | _heads(verbose, resolve_dependencies) 16 | -------------------------------------------------------------------------------- /edgy/cli/operations/history.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import history as _history 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.option( 9 | "-r", "--rev-range", default=None, help="Specify a revision range; format is [start]:[end]" 10 | ) 11 | @click.option("-v", "--verbose", is_flag=True, help="Use more verbose output") 12 | @click.option( 13 | "-i", 14 | "--indicate-current", 15 | is_flag=True, 16 | help=("Indicate current version (Alembic 0.9.9 or greater is required)"), 17 | ) 18 | @click.command() 19 | def history(rev_range: str, verbose: bool, indicate_current: bool) -> None: 20 | """List changeset scripts in chronological order.""" 21 | _history(rev_range, verbose, indicate_current) 22 | -------------------------------------------------------------------------------- /edgy/cli/operations/init.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import init as _init 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.option( 9 | "-t", "--template", default=None, help=('Repository template to use (default is "default")') 10 | ) 11 | @click.option( 12 | "--package", 13 | is_flag=True, 14 | help=("Write empty __init__.py files to the environment and version locations"), 15 | ) 16 | @click.command(name="init") 17 | def init(template: str, package: bool) -> None: 18 | """Creates a new migration repository.""" 19 | _init(template, package) 20 | -------------------------------------------------------------------------------- /edgy/cli/operations/inspectdb.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.utils.inspect import InspectDB 4 | 5 | 6 | @click.option( 7 | "--database", 8 | required=True, 9 | help=("Connection string. Example: postgres+asyncpg://user:password@localhost:5432/my_db"), 10 | ) 11 | @click.option( 12 | "--schema", 13 | default=None, 14 | help=("Database schema to be applied."), 15 | ) 16 | @click.command() 17 | def inspect_db( 18 | database: str, 19 | schema: str | None = None, 20 | ) -> None: 21 | """ 22 | Inspects an existing database and generates the Edgy reflect models. 23 | """ 24 | inspect_db = InspectDB(database=database, schema=schema) 25 | inspect_db.inspect() 26 | -------------------------------------------------------------------------------- /edgy/cli/operations/list_templates.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client to interact with Edgy models and migrations. 3 | """ 4 | 5 | import click 6 | 7 | from edgy.cli.base import list_templates as template_list 8 | 9 | 10 | @click.command(name="list-templates") 11 | def list_templates() -> None: 12 | """ 13 | Lists all the available templates available to Edgy 14 | """ 15 | template_list() 16 | -------------------------------------------------------------------------------- /edgy/cli/operations/merge.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import click 4 | 5 | from edgy.cli.base import merge as _merge 6 | from edgy.cli.decorators import add_migration_directory_option 7 | 8 | 9 | @add_migration_directory_option 10 | @click.option("-m", "--message", default=None, help="Merge revision message") 11 | @click.option( 12 | "--branch-label", default=None, help=("Specify a branch label to apply to the new revision") 13 | ) 14 | @click.option( 15 | "--rev-id", default=None, help=("Specify a hardcoded revision id instead of generating one") 16 | ) 17 | @click.command() 18 | @click.argument("revisions", nargs=-1) 19 | def merge(message: str, branch_label: str, rev_id: str, revisions: Any) -> None: 20 | """Merge two revisions together, creating a new revision file""" 21 | _merge(revisions, message, branch_label, rev_id) 22 | -------------------------------------------------------------------------------- /edgy/cli/operations/migrate.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import click 4 | 5 | from edgy.cli.base import upgrade as _upgrade 6 | from edgy.cli.decorators import add_migration_directory_option 7 | 8 | 9 | @add_migration_directory_option 10 | @click.option( 11 | "--sql", is_flag=True, help=("Don't emit SQL to database - dump to standard output instead") 12 | ) 13 | @click.option( 14 | "--tag", default=None, help=('Arbitrary "tag" name - can be used by custom env.py scripts') 15 | ) 16 | @click.option( 17 | "-x", "--arg", multiple=True, help="Additional arguments consumed by custom env.py scripts" 18 | ) 19 | @click.command(context_settings={"ignore_unknown_options": True}) 20 | @click.argument("revision", default="head") 21 | def migrate(sql: bool, tag: str, arg: Any, revision: str) -> None: 22 | """ 23 | Upgrades to the latest version or to a specific version 24 | provided by the --tag. 25 | """ 26 | _upgrade(revision, sql, tag, arg) 27 | -------------------------------------------------------------------------------- /edgy/cli/operations/shell/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import shell as shell # noqa 2 | -------------------------------------------------------------------------------- /edgy/cli/operations/shell/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ShellOption(str, Enum): 5 | IPYTHON = "ipython" 6 | PTPYTHON = "ptpython" 7 | PYTHON = "python" 8 | -------------------------------------------------------------------------------- /edgy/cli/operations/show.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import show as _show 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.command() 9 | @click.argument("revision", default="head") 10 | def show(revision: str) -> None: 11 | """Show the revision denoted by the given symbol.""" 12 | _show(revision) 13 | -------------------------------------------------------------------------------- /edgy/cli/operations/stamp.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from edgy.cli.base import stamp as _stamp 4 | from edgy.cli.decorators import add_migration_directory_option 5 | 6 | 7 | @add_migration_directory_option 8 | @click.option( 9 | "--sql", is_flag=True, help=("Don't emit SQL to database - dump to standard output instead") 10 | ) 11 | @click.option( 12 | "--tag", default=None, help=('Arbitrary "tag" name - can be used by custom env.py scripts') 13 | ) 14 | @click.argument("revision", default="head") 15 | @click.command() 16 | def stamp(sql: bool, tag: str, revision: str) -> None: 17 | """'stamp' the revision table with the given revision; don't run any 18 | migrations""" 19 | _stamp(revision, sql, tag) 20 | -------------------------------------------------------------------------------- /edgy/cli/templates/default/README: -------------------------------------------------------------------------------- 1 | Database configuration with Alembic. 2 | -------------------------------------------------------------------------------- /edgy/cli/templates/default/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,edgy 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_edgy] 38 | level = INFO 39 | handlers = 40 | qualname = edgy 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /edgy/cli/templates/plain/README: -------------------------------------------------------------------------------- 1 | Database configuration with Alembic. 2 | -------------------------------------------------------------------------------- /edgy/cli/templates/plain/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,edgy 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_edgy] 38 | level = INFO 39 | handlers = 40 | qualname = edgy 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /edgy/cli/templates/sequencial/README: -------------------------------------------------------------------------------- 1 | Database configuration with Alembic with sequencial revision. 2 | -------------------------------------------------------------------------------- /edgy/cli/templates/sequencial/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | file_template = %%(rev)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | script_location = . 11 | timezone = UTC 12 | truncate_slug_length = 40 13 | output_encoding = utf-8 14 | 15 | # Logging configuration 16 | [loggers] 17 | keys = root,sqlalchemy,alembic,edgy 18 | 19 | [handlers] 20 | keys = console 21 | 22 | [formatters] 23 | keys = generic 24 | 25 | [logger_root] 26 | level = WARN 27 | handlers = console 28 | qualname = 29 | 30 | [logger_sqlalchemy] 31 | level = WARN 32 | handlers = 33 | qualname = sqlalchemy.engine 34 | 35 | [logger_alembic] 36 | level = INFO 37 | handlers = 38 | qualname = alembic 39 | 40 | [logger_edgy] 41 | level = INFO 42 | handlers = 43 | qualname = edgy 44 | 45 | [handler_console] 46 | class = StreamHandler 47 | args = (sys.stderr,) 48 | level = NOTSET 49 | formatter = generic 50 | 51 | [formatter_generic] 52 | format = %(levelname)-5.5s [%(name)s] %(message)s 53 | datefmt = %H:%M:%S 54 | -------------------------------------------------------------------------------- /edgy/cli/templates/url/README: -------------------------------------------------------------------------------- 1 | Database configuration with Alembic. 2 | -------------------------------------------------------------------------------- /edgy/cli/templates/url/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,edgy 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_edgy] 38 | level = INFO 39 | handlers = 40 | qualname = edgy 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /edgy/conf/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import lru_cache 4 | from typing import TYPE_CHECKING, Any, cast 5 | 6 | if TYPE_CHECKING: 7 | from monkay import Monkay 8 | 9 | from edgy import EdgySettings, Instance 10 | 11 | 12 | @lru_cache 13 | def get_edgy_monkay() -> Monkay[Instance, EdgySettings]: 14 | from edgy import monkay 15 | 16 | monkay.evaluate_settings(on_conflict="error", ignore_import_errors=False) 17 | 18 | return monkay 19 | 20 | 21 | class SettingsForward: 22 | def __getattribute__(self, name: str) -> Any: 23 | monkay = get_edgy_monkay() 24 | return getattr(monkay.settings, name) 25 | 26 | 27 | settings: EdgySettings = cast("EdgySettings", SettingsForward()) 28 | 29 | 30 | def evaluate_settings_once_ready() -> None: 31 | """ 32 | Call when settings must be ready. 33 | 34 | This doesn't prevent the settings being updated later or set before. 35 | """ 36 | get_edgy_monkay() 37 | 38 | 39 | __all__ = ["settings", "evaluate_settings_once_ready"] 40 | -------------------------------------------------------------------------------- /edgy/conf/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from warnings import warn 3 | 4 | warn( 5 | "This module is deprecated. Use `esmerald.conf.EnvironmentType` instead when using Esmerald " 6 | "or define otherwise your own EnvironmentType.", 7 | DeprecationWarning, 8 | stacklevel=2, 9 | ) 10 | 11 | 12 | class EnvironmentType(str, Enum): 13 | """An Enum for environments.""" 14 | 15 | DEVELOPMENT = "development" 16 | TESTING = "testing" 17 | PRODUCTION = "production" 18 | -------------------------------------------------------------------------------- /edgy/conf/module_import.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from typing import Any 3 | from warnings import warn 4 | 5 | warn( 6 | "This module is deprecated. Use `monkay.load` instead.", 7 | DeprecationWarning, 8 | stacklevel=2, 9 | ) 10 | 11 | 12 | def import_string(dotted_path: str) -> Any: 13 | """ 14 | Import a dotted module path and return the attribute/class designated by the 15 | last name in the path. Raise ImportError if the import failed. 16 | """ 17 | try: 18 | module_path, class_name = dotted_path.rsplit(".", 1) 19 | except ValueError as err: 20 | raise ImportError(f"{dotted_path} doesn't look like a module path") from err 21 | 22 | module = import_module(module_path) 23 | 24 | try: 25 | return getattr(module, class_name) 26 | except AttributeError as err: 27 | raise ImportError( 28 | f'Module "{module_path}" does not define a "{class_name}" attribute/class' 29 | ) from err 30 | -------------------------------------------------------------------------------- /edgy/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/contrib/__init__.py -------------------------------------------------------------------------------- /edgy/contrib/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import create_admin_app 2 | 3 | __all__ = ["create_admin_app"] 4 | -------------------------------------------------------------------------------- /edgy/contrib/admin/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pydantic import Field 4 | from pydantic_settings import BaseSettings, SettingsConfigDict 5 | 6 | 7 | class AdminConfig(BaseSettings): 8 | model_config = SettingsConfigDict(extra="allow", arbitrary_types_allowed=True) 9 | admin_prefix_url: str = "/admin" 10 | title: str = "Edgy Admin" 11 | menu_title: str = "Edgy Admin" 12 | favicon: str = "https://raw.githubusercontent.com/dymmond/edgy/refs/heads/main/docs/statics/images/favicon.ico" 13 | sidebar_bg_colour: str = "#1C4C74" 14 | dashboard_title: str = "Edgy Admin Dashboard" 15 | SECRET_KEY: str | bytes = Field(default_factory=lambda: os.urandom(64)) 16 | -------------------------------------------------------------------------------- /edgy/contrib/admin/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found - 404 7 | {# Link to your compiled Tailwind CSS file #} 8 | 9 | 10 | 11 |
12 |

404

13 |

14 | Sorry, we couldn't find the page you're looking for. 15 |

16 |

17 | The page you requested might have been moved or doesn't exist. 18 |

19 | 20 | Go back to the homepage 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /edgy/contrib/admin/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/contrib/admin/utils/__init__.py -------------------------------------------------------------------------------- /edgy/contrib/admin/utils/messages.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from lilya.context import session 4 | 5 | 6 | def add_message(level: str, message: str) -> None: 7 | """ 8 | Stores a message in the session for rendering in the next request. 9 | Level can be: success, info, warning, error. 10 | """ 11 | if not hasattr(session, "messages"): 12 | session.messages = [] 13 | cast(list, session.messages).append({"level": level, "text": message}) 14 | 15 | 16 | def get_messages(peek: bool = False) -> list: 17 | """ 18 | Retrieves and clears messages from the session. 19 | """ 20 | if not hasattr(session, "messages"): 21 | return [] 22 | messages = cast(list, session.messages) 23 | if peek: 24 | return messages 25 | _messages = messages.copy() 26 | messages.clear() 27 | return _messages 28 | -------------------------------------------------------------------------------- /edgy/contrib/autoreflection/__init__.py: -------------------------------------------------------------------------------- 1 | from .models import AutoReflectModel 2 | 3 | __all__ = ["AutoReflectModel"] 4 | -------------------------------------------------------------------------------- /edgy/contrib/autoreflection/models.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, ClassVar 2 | 3 | import edgy 4 | 5 | from .metaclasses import AutoReflectionMeta, AutoReflectionMetaInfo 6 | 7 | if TYPE_CHECKING: 8 | from edgy.core.db.models.types import BaseModelType 9 | 10 | 11 | class AutoReflectModel(edgy.ReflectModel, metaclass=AutoReflectionMeta): 12 | meta: ClassVar[AutoReflectionMetaInfo] 13 | 14 | @classmethod 15 | def real_add_to_registry(cls, **kwargs: Any) -> type["BaseModelType"]: 16 | if isinstance(cls.meta, AutoReflectionMetaInfo): 17 | kwargs.setdefault("registry_type_name", "pattern_models") 18 | return super().real_add_to_registry(**kwargs) 19 | -------------------------------------------------------------------------------- /edgy/contrib/contenttypes/__init__.py: -------------------------------------------------------------------------------- 1 | from .fields import ContentTypeField 2 | from .models import ContentType 3 | 4 | __all__ = ["ContentTypeField", "ContentType"] 5 | -------------------------------------------------------------------------------- /edgy/contrib/contenttypes/metaclasses.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from edgy.core.db.models.metaclasses import ( 4 | BaseModelMeta, 5 | ) 6 | 7 | 8 | class ContentTypeMeta(BaseModelMeta): 9 | def __new__( 10 | cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any], **kwargs: Any 11 | ) -> type: 12 | new_model = super().__new__(cls, name, bases, attrs, **kwargs) 13 | if new_model.no_constraint: 14 | new_model.__require_model_based_deletion__ = True 15 | return new_model 16 | -------------------------------------------------------------------------------- /edgy/contrib/multi_tenancy/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import TenantModel 2 | from .registry import TenantRegistry 3 | from .settings import TenancySettings 4 | 5 | __all__ = ["TenantModel", "TenantRegistry", "TenancySettings"] 6 | -------------------------------------------------------------------------------- /edgy/contrib/multi_tenancy/registry.py: -------------------------------------------------------------------------------- 1 | from edgy.core.connection.registry import Registry as TenantRegistry 2 | 3 | __all__ = ["TenantRegistry"] 4 | -------------------------------------------------------------------------------- /edgy/contrib/multi_tenancy/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import cached_property 3 | from typing import Any 4 | 5 | from pydantic_settings import SettingsConfigDict 6 | 7 | from edgy.conf.global_settings import EdgySettings 8 | 9 | 10 | class TenancySettings(EdgySettings): 11 | """ 12 | BaseSettings used for the contrib of Edgy tenancy 13 | """ 14 | 15 | model_config = SettingsConfigDict(extra="allow", ignored_types=(cached_property,)) 16 | auto_create_schema: bool = True 17 | auto_drop_schema: bool = False 18 | tenant_schema_default: str = "public" 19 | tenant_model: str | None = None 20 | domain: Any = os.getenv("DOMAIN") 21 | domain_name: str = "localhost" 22 | auth_user_model: str | None = None 23 | -------------------------------------------------------------------------------- /edgy/contrib/multi_tenancy/utils.py: -------------------------------------------------------------------------------- 1 | from edgy.core.tenancy.utils import create_schema, create_tables 2 | 3 | __all__ = [ 4 | "create_tables", 5 | "create_schema", 6 | ] 7 | -------------------------------------------------------------------------------- /edgy/contrib/pagination/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import BasePage, BasePaginator, Page, Paginator 2 | from .cursor import CursorPage, CursorPaginator 3 | 4 | __all__ = ["BasePage", "BasePaginator", "Page", "Paginator", "CursorPage", "CursorPaginator"] 5 | -------------------------------------------------------------------------------- /edgy/contrib/permissions/__init__.py: -------------------------------------------------------------------------------- 1 | from .managers import PermissionManager 2 | from .models import BasePermission 3 | 4 | __all__ = ["PermissionManager", "BasePermission"] 5 | -------------------------------------------------------------------------------- /edgy/contrib/permissions/models.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import edgy 4 | 5 | from .managers import PermissionManager 6 | 7 | 8 | class BasePermission(edgy.Model): 9 | users_field_group: ClassVar[str] = "users" 10 | name: str = edgy.fields.CharField(max_length=100, null=False) 11 | description: str | None = edgy.fields.ComputedField( # type: ignore 12 | getter="get_description", 13 | setter="set_description", 14 | fallback_getter=lambda field, instance, owner: instance.name, 15 | ) 16 | 17 | query = PermissionManager() 18 | 19 | class Meta: 20 | abstract = True 21 | -------------------------------------------------------------------------------- /edgy/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/core/__init__.py -------------------------------------------------------------------------------- /edgy/core/connection/__init__.py: -------------------------------------------------------------------------------- 1 | from .database import Database, DatabaseURL 2 | from .registry import Registry 3 | 4 | __all__ = ["Database", "DatabaseURL", "Registry"] 5 | -------------------------------------------------------------------------------- /edgy/core/connection/database.py: -------------------------------------------------------------------------------- 1 | from databasez import Database, DatabaseURL 2 | 3 | __all__ = ["Database", "DatabaseURL"] 4 | -------------------------------------------------------------------------------- /edgy/core/datastructures.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class HashableBaseModel(BaseModel): 7 | """ 8 | Pydantic BaseModel by default doesn't handle with hashable types the same way 9 | a python object would and therefore there are types that are mutable (list, set) 10 | not hashable and those need to be handled properly. 11 | 12 | HashableBaseModel handles those corner cases. 13 | """ 14 | 15 | __slots__ = ["__weakref__"] 16 | 17 | def __hash__(self) -> Any: 18 | values: Any = {} 19 | for key, value in self.__dict__.items(): 20 | values[key] = None 21 | if isinstance(value, list | set): 22 | values[key] = tuple(value) 23 | else: 24 | values[key] = value 25 | return hash((type(self),) + tuple(values)) 26 | 27 | 28 | class ArbitraryHashableBaseModel(HashableBaseModel): 29 | """ 30 | Same as HashableBaseModel but allowing arbitrary values 31 | """ 32 | 33 | class Config: 34 | extra = "allow" 35 | arbitrary_types_allowed = True 36 | -------------------------------------------------------------------------------- /edgy/core/db/__init__.py: -------------------------------------------------------------------------------- 1 | from .context_vars import set_schema, set_tenant, with_schema, with_tenant 2 | 3 | __all__ = ["set_tenant", "with_tenant", "with_schema", "set_schema"] 4 | -------------------------------------------------------------------------------- /edgy/core/db/constants.py: -------------------------------------------------------------------------------- 1 | CASCADE = "CASCADE" 2 | RESTRICT = "RESTRICT" 3 | DO_NOTHING = "DO NOTHING" 4 | SET_NULL = "SET NULL" 5 | SET_DEFAULT = "SET DEFAULT" 6 | PROTECT = "PROTECT" 7 | 8 | 9 | class OLD_M2M_NAMING: ... 10 | 11 | 12 | class NEW_M2M_NAMING: ... 13 | 14 | 15 | class ConditionalRedirect(dict): ... 16 | 17 | 18 | __all__ = [ 19 | "CASCADE", 20 | "RESTRICT", 21 | "DO_NOTHING", 22 | "SET_NULL", 23 | "SET_DEFAULT", 24 | "PROTECT", 25 | "ConditionalRedirect", 26 | ] 27 | -------------------------------------------------------------------------------- /edgy/core/db/fields/_internal.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | from typing import Any 3 | 4 | import sqlalchemy 5 | 6 | 7 | class IPAddress(sqlalchemy.TypeDecorator): 8 | impl: Any = sqlalchemy.String 9 | cache_ok: bool = True 10 | 11 | def load_dialect_impl(self, dialect: Any) -> Any: 12 | if dialect.name not in {"postgres", "postgresql"}: 13 | return dialect.type_descriptor(sqlalchemy.String(length=45)) 14 | return dialect.type_descriptor(sqlalchemy.dialects.postgresql.INET()) 15 | 16 | def process_bind_param(self, value: Any, dialect: Any) -> Any: 17 | if value is None: 18 | return value 19 | return str(value) 20 | 21 | def process_result_value(self, value: Any, dialect: Any) -> Any: 22 | if value is None: 23 | return value 24 | if not isinstance(value, ipaddress.IPv4Address | ipaddress.IPv6Address): 25 | value = ipaddress.ip_address(value) 26 | return value 27 | -------------------------------------------------------------------------------- /edgy/core/db/fields/one_to_one_keys.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any 4 | 5 | from edgy.core.db.fields.foreign_keys import ForeignKey 6 | from edgy.core.terminal import Print 7 | 8 | if TYPE_CHECKING: 9 | from edgy.core.db.fields.types import BaseFieldType 10 | from edgy.core.db.models.types import BaseModelType 11 | 12 | 13 | terminal = Print() 14 | 15 | 16 | class OneToOneField(ForeignKey): 17 | """ 18 | Representation of a one to one field. 19 | """ 20 | 21 | def __new__( 22 | cls, 23 | to: type[BaseModelType] | str, 24 | **kwargs: Any, 25 | ) -> BaseFieldType: 26 | for argument in ["index", "unique"]: 27 | if argument in kwargs: 28 | terminal.write_warning(f"Declaring {argument} on a OneToOneField has no effect.") 29 | kwargs["unique"] = True 30 | 31 | return super().__new__(cls, to=to, **kwargs) 32 | 33 | 34 | OneToOne = OneToOneField 35 | -------------------------------------------------------------------------------- /edgy/core/db/fields/place_holder_field.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from edgy.core.db.fields.factories import FieldFactory 4 | from edgy.core.db.fields.types import BaseFieldType 5 | 6 | 7 | class PlaceholderField(FieldFactory): 8 | """Placeholder field, without db column""" 9 | 10 | def __new__( 11 | cls, 12 | *, 13 | pydantic_field_type: Any = Any, 14 | **kwargs: Any, 15 | ) -> BaseFieldType: 16 | kwargs.setdefault("exclude", True) 17 | return super().__new__(cls, pydantic_field_type=pydantic_field_type, **kwargs) 18 | 19 | def clean( 20 | self, 21 | name: str, 22 | value: Any, 23 | for_query: bool = False, 24 | ) -> dict[str, Any]: 25 | return {} 26 | 27 | @classmethod 28 | def get_pydantic_type(cls, kwargs: dict[str, Any]) -> Any: 29 | """Returns the type for pydantic""" 30 | return kwargs.pop("pydantic_field_type") 31 | -------------------------------------------------------------------------------- /edgy/core/db/models/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from monkay import Monkay 4 | 5 | if TYPE_CHECKING: 6 | from .managers import Manager, RedirectManager 7 | from .model import Model, ReflectModel, StrictModel 8 | from .model_reference import ModelRef 9 | 10 | __all__ = ["Model", "StrictModel", "ModelRef", "ReflectModel", "Manager", "RedirectManager"] 11 | 12 | Monkay( 13 | globals(), 14 | lazy_imports={ 15 | "Model": ".model.Model", 16 | "ReflectModel": ".model.ReflectModel", 17 | "StrictModel": ".model.StrictModel", 18 | "ModelRef": ".model_reference.ModelRef", 19 | "Manager": ".managers.Manager", 20 | "RedirectManager": ".managers.RedirectManager", 21 | }, 22 | ) 23 | -------------------------------------------------------------------------------- /edgy/core/db/models/mixins/__init__.py: -------------------------------------------------------------------------------- 1 | from .db import DatabaseMixin 2 | from .generics import DeclarativeMixin 3 | from .reflection import ReflectedModelMixin 4 | from .row import ModelRowMixin 5 | 6 | __all__ = ["DeclarativeMixin", "ModelRowMixin", "ReflectedModelMixin", "DatabaseMixin"] 7 | -------------------------------------------------------------------------------- /edgy/core/db/models/model_reference.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class ModelRef(BaseModel): 7 | __related_name__: ClassVar[str] 8 | -------------------------------------------------------------------------------- /edgy/core/db/querysets/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from monkay import Monkay 4 | 5 | if TYPE_CHECKING: 6 | from .base import QuerySet 7 | from .clauses import Q, and_, not_, or_ 8 | from .prefetch import Prefetch 9 | 10 | __all__ = ["QuerySet", "Q", "and_", "not_", "or_", "Prefetch"] 11 | 12 | Monkay( 13 | globals(), 14 | lazy_imports={ 15 | "QuerySet": ".base.QuerySet", 16 | "Q": ".clauses.Q", 17 | "and_": ".clauses.and_", 18 | "not_": ".clauses.not_", 19 | "or_": ".clauses.or_", 20 | "Prefetch": ".prefetch.Prefetch", 21 | }, 22 | ) 23 | -------------------------------------------------------------------------------- /edgy/core/db/relationships/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/core/db/relationships/__init__.py -------------------------------------------------------------------------------- /edgy/core/files/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from monkay import Monkay 4 | 5 | if TYPE_CHECKING: 6 | from .base import ContentFile, FieldFile, File, ImageFieldFile 7 | from .storage import Storage, storages 8 | Monkay( 9 | globals(), 10 | lazy_imports={ 11 | "Storage": ".base.Storage", 12 | "storages": ".storage.storages", 13 | "ContentFile": ".base.ContentFile", 14 | "File": ".base.File", 15 | "FieldFile": ".base.FieldFile", 16 | "ImageFieldFile": ".base.ImageFieldFile", 17 | }, 18 | uncached_imports={"storages"}, 19 | ) 20 | 21 | __all__ = ["File", "ContentFile", "FieldFile", "ImageFieldFile", "Storage", "storages"] 22 | -------------------------------------------------------------------------------- /edgy/core/files/storage/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from functools import lru_cache 4 | from typing import TYPE_CHECKING 5 | 6 | from monkay import Monkay 7 | 8 | from .handler import StorageHandler 9 | 10 | if TYPE_CHECKING: 11 | from edgy import EdgySettings, Instance 12 | 13 | from .base import Storage 14 | storages: StorageHandler 15 | 16 | 17 | _fallback_storage = StorageHandler() 18 | 19 | 20 | @lru_cache 21 | def _get_monkay() -> Monkay[Instance, EdgySettings]: 22 | from edgy import monkay 23 | 24 | return monkay 25 | 26 | 27 | def _get_storages() -> StorageHandler: 28 | instance = _get_monkay().instance 29 | return _fallback_storage if instance is None else instance.storages 30 | 31 | 32 | Monkay( 33 | globals(), 34 | lazy_imports={ 35 | "Storage": ".base.Storage", 36 | "storages": _get_storages, 37 | }, 38 | uncached_imports={"storages"}, 39 | ) 40 | 41 | __all__ = ["Storage", "StorageHandler", "storages"] 42 | -------------------------------------------------------------------------------- /edgy/core/marshalls/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Marshall 2 | from .config import ConfigMarshall 3 | 4 | __all__ = ["ConfigMarshall", "Marshall"] 5 | -------------------------------------------------------------------------------- /edgy/core/marshalls/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, TypedDict 4 | 5 | if TYPE_CHECKING: 6 | from edgy.core.db.models import Model 7 | 8 | 9 | class ConfigMarshall(TypedDict, total=False): 10 | """A TypedDict for configuring Marshall behaviour.""" 11 | 12 | model: type[Model] | str 13 | """The model from there the marshall will read from.""" 14 | 15 | fields: list[str] | None = None 16 | """A list of fields to be serialized""" 17 | 18 | exclude: list[str] | None = None 19 | """A list of fields to be excluded from the serialization.""" 20 | -------------------------------------------------------------------------------- /edgy/core/tenancy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/core/tenancy/__init__.py -------------------------------------------------------------------------------- /edgy/core/terminal/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import OutputColour 2 | from .print import Print 3 | from .terminal import Terminal 4 | 5 | __all__ = ["OutputColour", "Print", "Terminal"] 6 | -------------------------------------------------------------------------------- /edgy/core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/core/utils/__init__.py -------------------------------------------------------------------------------- /edgy/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/protocols/__init__.py -------------------------------------------------------------------------------- /edgy/protocols/many_relationship.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Optional, Protocol, runtime_checkable 2 | 3 | if TYPE_CHECKING: # pragma: nocover 4 | from edgy.core.db.models.types import BaseModelType 5 | 6 | 7 | @runtime_checkable 8 | class ManyRelationProtocol(Protocol): 9 | instance: "BaseModelType" 10 | 11 | """Defines the what needs to be implemented when using the ManyRelationProtocol""" 12 | 13 | async def save_related(self) -> None: ... 14 | 15 | async def create(self, *args: Any, **kwargs: Any) -> Optional["BaseModelType"]: ... 16 | 17 | async def add(self, child: "BaseModelType") -> Optional["BaseModelType"]: ... 18 | 19 | def stage(self, *children: "BaseModelType") -> None: 20 | """Lazy add children""" 21 | 22 | async def remove(self, child: Optional["BaseModelType"] = None) -> None: ... 23 | -------------------------------------------------------------------------------- /edgy/protocols/transaction_call.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any, Protocol 4 | 5 | if TYPE_CHECKING: 6 | from databasez.core.transaction import Transaction 7 | 8 | 9 | class TransactionCallProtocol(Protocol): 10 | def __call__(instance: Any, *, force_rollback: bool = False, **kwargs: Any) -> Transaction: ... 11 | -------------------------------------------------------------------------------- /edgy/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/py.typed -------------------------------------------------------------------------------- /edgy/testclient.py: -------------------------------------------------------------------------------- 1 | from edgy.testing.client import DatabaseTestClient 2 | 3 | __all__ = ["DatabaseTestClient"] 4 | -------------------------------------------------------------------------------- /edgy/testing/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from monkay import Monkay 4 | 5 | if TYPE_CHECKING: 6 | from .client import DatabaseTestClient 7 | from .factory import ( 8 | FactoryField, 9 | ListSubFactory, 10 | ModelFactory, 11 | ModelFactoryContext, 12 | SubFactory, 13 | ) 14 | 15 | __all__ = [ 16 | "DatabaseTestClient", 17 | "ModelFactory", 18 | "SubFactory", 19 | "ListSubFactory", 20 | "FactoryField", 21 | "ModelFactoryContext", 22 | ] 23 | 24 | 25 | Monkay( 26 | globals(), 27 | lazy_imports={ 28 | "ModelFactory": ".factory.ModelFactory", 29 | "ModelFactoryContext": ".factory.ModelFactoryContext", 30 | "SubFactory": ".factory.SubFactory", 31 | "ListSubFactory": ".factory.ListSubFactory", 32 | "FactoryField": ".factory.FactoryField", 33 | "DatabaseTestClient": ".client.DatabaseTestClient", 34 | }, 35 | ) 36 | del Monkay 37 | -------------------------------------------------------------------------------- /edgy/testing/exceptions.py: -------------------------------------------------------------------------------- 1 | from edgy.exceptions import EdgyException 2 | 3 | 4 | class InvalidModelError(EdgyException): ... 5 | 6 | 7 | class ExcludeValue(BaseException): ... 8 | -------------------------------------------------------------------------------- /edgy/testing/factory/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import ModelFactory 2 | from .fields import FactoryField 3 | from .subfactory import ListSubFactory, SubFactory 4 | from .types import ModelFactoryContext 5 | 6 | __all__ = ["ModelFactory", "FactoryField", "ListSubFactory", "SubFactory", "ModelFactoryContext"] 7 | -------------------------------------------------------------------------------- /edgy/testing/factory/context_vars.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from contextvars import ContextVar 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from .types import ModelFactoryContext 8 | 9 | model_factory_context: ContextVar[ModelFactoryContext] = ContextVar("model_factory_context") 10 | -------------------------------------------------------------------------------- /edgy/testing/factory/types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections.abc import Callable 4 | from typing import TYPE_CHECKING, Any, Protocol, TypeAlias, TypedDict, Union 5 | 6 | if TYPE_CHECKING: 7 | from faker import Faker 8 | 9 | from edgy.core.db.fields.types import BaseFieldType 10 | 11 | from .fields import FactoryField 12 | 13 | 14 | class _ModelFactoryContext(TypedDict): 15 | faker: Faker 16 | exclude_autoincrement: bool 17 | depth: int 18 | callcounts: dict[int, int] 19 | 20 | 21 | if TYPE_CHECKING: 22 | 23 | class ModelFactoryContext(Faker, _ModelFactoryContext, Protocol): 24 | pass 25 | else: 26 | ModelFactoryContext = _ModelFactoryContext 27 | 28 | 29 | FactoryParameterCallback = Callable[ 30 | [ 31 | "FactoryField", 32 | ModelFactoryContext, 33 | str, 34 | ], 35 | Any, 36 | ] 37 | FactoryParameters: TypeAlias = dict[str, Any | FactoryParameterCallback] 38 | FactoryCallback: TypeAlias = Callable[["FactoryField", ModelFactoryContext, dict[str, Any]], Any] 39 | FieldFactoryCallback: TypeAlias = str | FactoryCallback 40 | FactoryFieldType: TypeAlias = Union[str, "BaseFieldType", type["BaseFieldType"]] 41 | -------------------------------------------------------------------------------- /edgy/types.py: -------------------------------------------------------------------------------- 1 | from pydantic_core import PydanticUndefined 2 | 3 | Undefined = PydanticUndefined 4 | -------------------------------------------------------------------------------- /edgy/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/edgy/utils/__init__.py -------------------------------------------------------------------------------- /edgy/utils/compat.py: -------------------------------------------------------------------------------- 1 | from inspect import isclass 2 | from typing import Any, get_origin 3 | 4 | 5 | def is_class_and_subclass(value: Any, _type: Any) -> bool: 6 | """ 7 | Checks if a `value` is of type class and subclass. 8 | by checking the origin of the value against the type being 9 | verified. 10 | """ 11 | original = get_origin(value) 12 | if not original and not isclass(value): 13 | return False 14 | 15 | try: 16 | if original: 17 | return original and issubclass(original, _type) 18 | return issubclass(value, _type) 19 | except TypeError: 20 | return False 21 | -------------------------------------------------------------------------------- /scripts/clean: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ -d 'dist' ] ; then 4 | rm -r dist 5 | fi 6 | if [ -d 'site' ] ; then 7 | rm -r site 8 | fi 9 | if [ -d 'htmlcov' ] ; then 10 | rm -r htmlcov 11 | fi 12 | if [ -d 'edgy.egg-info' ] ; then 13 | rm -r edgy.egg-info 14 | fi 15 | if [ -d '.hypothesis' ] ; then 16 | rm -r .hypothesis 17 | fi 18 | if [ -d '.mypy_cache' ] ; then 19 | rm -r .mypy_cache 20 | fi 21 | if [ -d '.pytest_cache' ] ; then 22 | rm -r .pytest_cache 23 | fi 24 | if [ -d '.ruff_cache' ] ; then 25 | rm -r .ruff_cache 26 | fi 27 | 28 | find edgy -type f -name "*.py[co]" -delete 29 | find edgy -type d -name __pycache__ -delete 30 | -------------------------------------------------------------------------------- /scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # Use the Python executable provided from the `-p` option, or a default. 4 | [ "$1" = "-p" ] && PYTHON=$2 || PYTHON="python3" 5 | 6 | VENV="venv" 7 | 8 | set -x 9 | 10 | if [ "$VIRTUAL_ENV" != '' ]; then 11 | PIP="$VIRTUAL_ENV/bin/pip" 12 | elif [ -z "$GITHUB_ACTIONS" ]; then 13 | "$PYTHON" -m venv "$VENV" 14 | PIP="$VENV/bin/pip" 15 | else 16 | PIP="pip" 17 | fi 18 | 19 | "$PIP" install -U pip 20 | "$PIP" install -e .[all,testing,test,docs] 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/__init__.py -------------------------------------------------------------------------------- /tests/clauses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/clauses/__init__.py -------------------------------------------------------------------------------- /tests/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/cli/__init__.py -------------------------------------------------------------------------------- /tests/cli/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/cli/conftest.py -------------------------------------------------------------------------------- /tests/cli/custom_multidb/README: -------------------------------------------------------------------------------- 1 | Custom template 2 | -------------------------------------------------------------------------------- /tests/cli/custom_multidb/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A custom generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,saffier 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_saffier] 38 | level = INFO 39 | handlers = 40 | qualname = saffier 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /tests/cli/custom_multidb/script.py.mako: -------------------------------------------------------------------------------- 1 | # Custom mako template 2 | """${message} 3 | 4 | Revision ID: ${up_revision} 5 | Revises: ${down_revision | comma,n} 6 | Create Date: ${create_date} 7 | 8 | """ 9 | from alembic import op 10 | import sqlalchemy as sa 11 | ${imports if imports else ""} 12 | 13 | # revision identifiers, used by Alembic. 14 | revision = ${repr(up_revision)} 15 | down_revision = ${repr(down_revision)} 16 | branch_labels = ${repr(branch_labels)} 17 | depends_on = ${repr(depends_on)} 18 | 19 | def upgrade(edgy_dbname: str = "") -> None: 20 | globals()[f"upgrade_{edgy_dbname}"]() 21 | 22 | 23 | def downgrade(edgy_dbname: str = "") -> None: 24 | globals()[f"downgrade_{edgy_dbname}"]() 25 | 26 | 27 | <% 28 | from edgy import monkay 29 | db_names = monkay.settings.migrate_databases 30 | %> 31 | 32 | ## generate an "upgrade_() / downgrade_()" function 33 | ## according to edgy migrate settings 34 | 35 | % for db_name in db_names: 36 | 37 | def ${f"upgrade_{db_name or ''}"}(): 38 | ${context.get(f"{db_name or ''}_upgrades", "pass")} 39 | 40 | 41 | def ${f"downgrade_{db_name or ''}"}(): 42 | ${context.get(f"{db_name or ''}_downgrades", "pass")} 43 | 44 | % endfor 45 | -------------------------------------------------------------------------------- /tests/cli/custom_multidb_copied_registry/README: -------------------------------------------------------------------------------- 1 | Custom template 2 | -------------------------------------------------------------------------------- /tests/cli/custom_multidb_copied_registry/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A custom generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,saffier 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_saffier] 38 | level = INFO 39 | handlers = 40 | qualname = saffier 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /tests/cli/custom_multidb_copied_registry/script.py.mako: -------------------------------------------------------------------------------- 1 | # Custom mako template 2 | """${message} 3 | 4 | Revision ID: ${up_revision} 5 | Revises: ${down_revision | comma,n} 6 | Create Date: ${create_date} 7 | 8 | """ 9 | from alembic import op 10 | import sqlalchemy as sa 11 | ${imports if imports else ""} 12 | 13 | # revision identifiers, used by Alembic. 14 | revision = ${repr(up_revision)} 15 | down_revision = ${repr(down_revision)} 16 | branch_labels = ${repr(branch_labels)} 17 | depends_on = ${repr(depends_on)} 18 | 19 | def upgrade(edgy_dbname: str = "") -> None: 20 | globals()[f"upgrade_{edgy_dbname}"]() 21 | 22 | 23 | def downgrade(edgy_dbname: str = "") -> None: 24 | globals()[f"downgrade_{edgy_dbname}"]() 25 | 26 | 27 | <% 28 | from edgy import monkay 29 | db_names = monkay.settings.migrate_databases 30 | %> 31 | 32 | ## generate an "upgrade_() / downgrade_()" function 33 | ## according to edgy migrate settings 34 | 35 | % for db_name in db_names: 36 | 37 | def ${f"upgrade_{db_name or ''}"}(): 38 | ${context.get(f"{db_name or ''}_upgrades", "pass")} 39 | 40 | 41 | def ${f"downgrade_{db_name or ''}"}(): 42 | ${context.get(f"{db_name or ''}_downgrades", "pass")} 43 | 44 | % endfor 45 | -------------------------------------------------------------------------------- /tests/cli/custom_singledb/README: -------------------------------------------------------------------------------- 1 | Custom template 2 | -------------------------------------------------------------------------------- /tests/cli/custom_singledb/alembic.ini.mako: -------------------------------------------------------------------------------- 1 | # A custom generic database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,saffier 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_saffier] 38 | level = INFO 39 | handlers = 40 | qualname = saffier 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /tests/cli/custom_singledb/script.py.mako: -------------------------------------------------------------------------------- 1 | # Custom mako template 2 | """${message} 3 | 4 | Revision ID: ${up_revision} 5 | Revises: ${down_revision | comma,n} 6 | Create Date: ${create_date} 7 | 8 | """ 9 | from alembic import op 10 | import sqlalchemy as sa 11 | ${imports if imports else ""} 12 | 13 | # revision identifiers, used by Alembic. 14 | revision = ${repr(up_revision)} 15 | down_revision = ${repr(down_revision)} 16 | branch_labels = ${repr(branch_labels)} 17 | depends_on = ${repr(depends_on)} 18 | 19 | 20 | def upgrade(): 21 | ${upgrades if upgrades else "pass"} 22 | 23 | 24 | def downgrade(): 25 | ${downgrades if downgrades else "pass"} 26 | -------------------------------------------------------------------------------- /tests/cli/test_alembic.py: -------------------------------------------------------------------------------- 1 | def test_alembic_version(): 2 | from edgy.cli import alembic_version 3 | 4 | assert len(alembic_version) == 3 5 | 6 | for v in alembic_version: 7 | assert isinstance(v, int) 8 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | os.environ.setdefault("EDGY_SETTINGS_MODULE", "tests.settings.default.TestSettings") 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def anyio_backend(): 10 | return ("asyncio", {"debug": True}) 11 | -------------------------------------------------------------------------------- /tests/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/contrib/__init__.py -------------------------------------------------------------------------------- /tests/factory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/factory/__init__.py -------------------------------------------------------------------------------- /tests/factory/generation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/factory/generation/__init__.py -------------------------------------------------------------------------------- /tests/fields/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/fields/__init__.py -------------------------------------------------------------------------------- /tests/foreign_keys/m2m_string/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/foreign_keys/m2m_string/__init__.py -------------------------------------------------------------------------------- /tests/foreign_keys/m2m_string_old/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/foreign_keys/m2m_string_old/__init__.py -------------------------------------------------------------------------------- /tests/images/mini_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/images/mini_image.jpg -------------------------------------------------------------------------------- /tests/images/mini_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/images/mini_image.png -------------------------------------------------------------------------------- /tests/marshalls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/marshalls/__init__.py -------------------------------------------------------------------------------- /tests/marshalls/save/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/marshalls/save/__init__.py -------------------------------------------------------------------------------- /tests/models/run_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/models/run_sync/__init__.py -------------------------------------------------------------------------------- /tests/pydantic_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/edgy/39c639967eb016f87674bc2ee728fb1e1f3a9eee/tests/pydantic_tests/__init__.py -------------------------------------------------------------------------------- /tests/settings/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | DATABASE_URL = os.environ.get( 4 | "TEST_DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:5432/edgy" 5 | ) 6 | 7 | DATABASE_ALTERNATIVE_URL = os.environ.get( 8 | "TEST_DATABASE_ALTERNATIVE_URL", 9 | "postgresql+asyncpg://postgres:postgres@localhost:5433/edgy_alt", 10 | ) 11 | 12 | TEST_DATABASE = "postgresql+asyncpg://postgres:postgres@localhost:5432/test_edgy" 13 | TEST_ALTERNATIVE_DATABASE = "postgresql+asyncpg://postgres:postgres@localhost:5433/test_edgy" 14 | -------------------------------------------------------------------------------- /tests/settings/default.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from edgy.contrib.multi_tenancy.settings import TenancySettings 5 | 6 | 7 | class TestSettings(TenancySettings): 8 | tenant_model: str = "Tenant" 9 | auth_user_model: str = "User" 10 | media_root: str | os.PathLike = Path(__file__).parent.parent / "test_media/" 11 | -------------------------------------------------------------------------------- /tests/settings/disabled_auto_server_defaults.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from edgy.contrib.multi_tenancy.settings import TenancySettings 5 | 6 | 7 | class TestSettings(TenancySettings): 8 | tenant_model: str = "Tenant" 9 | auth_user_model: str = "User" 10 | media_root: str | os.PathLike = Path(__file__).parent.parent / "test_media/" 11 | allow_auto_compute_server_defaults: bool = False 12 | -------------------------------------------------------------------------------- /tests/settings/multidb.py: -------------------------------------------------------------------------------- 1 | from edgy import EdgySettings 2 | 3 | 4 | class TestSettings(EdgySettings): 5 | migrate_databases: list[str | None] = [None, "another"] 6 | -------------------------------------------------------------------------------- /tests/settings/multidb_nonidentifier.py: -------------------------------------------------------------------------------- 1 | from edgy import EdgySettings 2 | 3 | 4 | class TestSettings(EdgySettings): 5 | migrate_databases: list[str | None] = [None, "ano ther "] 6 | -------------------------------------------------------------------------------- /tests/test_lazy_imports.py: -------------------------------------------------------------------------------- 1 | import edgy 2 | 3 | 4 | def test_lazy_imports(): 5 | missing = edgy.monkay.find_missing( 6 | all_var=edgy.__all__, 7 | search_pathes=[ 8 | ".core.connection", 9 | ".core.db.models", 10 | ".core.db.fields", 11 | ".core.db.constants", 12 | ], 13 | ) 14 | missing.pop("edgy.core.db.fields.BaseField") 15 | missing.pop("edgy.core.db.fields.BaseFieldType") 16 | assert not missing 17 | --------------------------------------------------------------------------------