diff --git a/.coverage b/.coverage index 8b03e5c..7f7ce46 100644 Binary files a/.coverage and b/.coverage differ diff --git a/README.md b/README.md index a2987d2..f7373a8 100644 --- a/README.md +++ b/README.md @@ -408,6 +408,83 @@ await Product.objects.create(name="Mars", company="Mars") ``` +Since version >=0.3.6 Ormar supports selecting subset of model columns to limit the data load +Warning - mandatory fields cannot be excluded as it will raise validation error, to exclude a field it has to be nullable. +pkcolumn cannot be excluded - it's always auto added +```python +import databases +import pydantic +import pytest +import sqlalchemy + +import ormar +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL, force_rollback=True) +metadata = sqlalchemy.MetaData() + + +class Company(ormar.Model): + class Meta: + tablename = "companies" + metadata = metadata + database = database + + id: ormar.Integer(primary_key=True) + name: ormar.String(max_length=100) + founded: ormar.Integer(nullable=True) + + +class Car(ormar.Model): + class Meta: + tablename = "cars" + metadata = metadata + database = database + + id: ormar.Integer(primary_key=True) + manufacturer: ormar.ForeignKey(Company) + name: ormar.String(max_length=100) + year: ormar.Integer(nullable=True) + gearbox_type: ormar.String(max_length=20, nullable=True) + gears: ormar.Integer(nullable=True) + aircon_type: ormar.String(max_length=20, nullable=True) + + + +# build some sample data +toyota = await Company.objects.create(name="Toyota", founded=1937) +await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, + aircon_type='Auto') + +# select manufacturer but only name - to include related models use notation {model_name}__{column} +all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all() +for car in all_cars: + # excluded columns will yield None + assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) + # included column on related models will be available, pk column is always included + # even if you do not include it in fields list + assert car.manufacturer.name == 'Toyota' + # also in the nested related models - you cannot exclude pk - it's always auto added + assert car.manufacturer.founded is None + +# fields() can be called several times, building up the columns to select +# models selected in select_related but with no columns in fields list implies all fields +all_cars = await Car.objects.select_related('manufacturer').fields('id').fields( + ['name']).all() +# all fiels from company model are selected +assert all_cars[0].manufacturer.name == 'Toyota' +assert all_cars[0].manufacturer.founded == 1937 + +# cannot exclude mandatory model columns - company__name in this example +await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__founded']).all() +# will raise pydantic ValidationError as company.name is required + +``` + ## Data types diff --git a/ormar/__init__.py b/ormar/__init__.py index 1616879..1765386 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -28,7 +28,7 @@ class UndefinedType: # pragma no cover Undefined = UndefinedType() -__version__ = "0.3.5" +__version__ = "0.3.6" __all__ = [ "Integer", "BigInteger", diff --git a/tests/test_foreign_keys.py b/tests/test_foreign_keys.py index 844d8b5..b85493d 100644 --- a/tests/test_foreign_keys.py +++ b/tests/test_foreign_keys.py @@ -233,8 +233,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name="Fantasies") - .all() + .filter(album__name="Fantasies") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -242,8 +242,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name__icontains="fan") - .all() + .filter(album__name__icontains="fan") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -288,8 +288,8 @@ async def test_multiple_fk(): members = ( await Member.objects.select_related("team__org") - .filter(team__org__ident="ACME Ltd") - .all() + .filter(team__org__ident="ACME Ltd") + .all() ) assert len(members) == 4 for member in members: @@ -321,8 +321,8 @@ async def test_pk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(position=2, album__name="Test") - .all() + .filter(position=2, album__name="Test") + .all() ) assert len(tracks) == 1 diff --git a/tests/test_selecting_subset_of_columns.py b/tests/test_selecting_subset_of_columns.py index 9c8f1bb..9397235 100644 --- a/tests/test_selecting_subset_of_columns.py +++ b/tests/test_selecting_subset_of_columns.py @@ -50,33 +50,69 @@ async def test_selecting_subset(): async with database: async with database.transaction(force_rollback=True): toyota = await Company.objects.create(name="Toyota", founded=1937) - await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, - aircon_type='Manual') - await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, - aircon_type='Manual') - await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, - aircon_type='Auto') + await Car.objects.create( + manufacturer=toyota, + name="Corolla", + year=2020, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", + ) + await Car.objects.create( + manufacturer=toyota, + name="Yaris", + year=2019, + gearbox_type="Manual", + gears=5, + aircon_type="Manual", + ) + await Car.objects.create( + manufacturer=toyota, + name="Supreme", + year=2020, + gearbox_type="Auto", + gears=6, + aircon_type="Auto", + ) - all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all() + all_cars = ( + await Car.objects.select_related("manufacturer") + .fields(["id", "name", "company__name"]) + .all() + ) for car in all_cars: - assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) - assert car.manufacturer.name == 'Toyota' + assert all( + getattr(car, x) is None + for x in ["year", "gearbox_type", "gears", "aircon_type"] + ) + assert car.manufacturer.name == "Toyota" assert car.manufacturer.founded is None - all_cars = await Car.objects.select_related('manufacturer').fields('id').fields( - ['name', 'manufacturer']).all() + all_cars = ( + await Car.objects.select_related("manufacturer") + .fields("id") + .fields(["name"]) + .all() + ) for car in all_cars: - assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) - assert car.manufacturer.name == 'Toyota' + assert all( + getattr(car, x) is None + for x in ["year", "gearbox_type", "gears", "aircon_type"] + ) + assert car.manufacturer.name == "Toyota" assert car.manufacturer.founded == 1937 - all_cars_check = await Car.objects.select_related('manufacturer').all() + all_cars_check = await Car.objects.select_related("manufacturer").all() for car in all_cars_check: - assert all(getattr(car, x) is not None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) - assert car.manufacturer.name == 'Toyota' + assert all( + getattr(car, x) is not None + for x in ["year", "gearbox_type", "gears", "aircon_type"] + ) + assert car.manufacturer.name == "Toyota" assert car.manufacturer.founded == 1937 with pytest.raises(pydantic.error_wrappers.ValidationError): # cannot exclude mandatory model columns - company__name in this example - await Car.objects.select_related('manufacturer').fields( - ['id', 'name', 'company__founded']).all() + await Car.objects.select_related("manufacturer").fields( + ["id", "name", "company__founded"] + ).all()