Skip to content

Commit c8f673e

Browse files
committed
test and fixing bugs
1 parent e4fd7a0 commit c8f673e

File tree

12 files changed

+472
-234
lines changed

12 files changed

+472
-234
lines changed

Changelog.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ Dataloom **`2.4.0`**
44

55
### Release Notes - `dataloom`
66

7-
We have release the new `dataloom` Version `2.6.0` (`2024-02-27`)
7+
We have release the new `dataloom` Version `2.4.0` (`2024-02-27`)
88

99
##### Features
1010

1111
- `sync` and `connect_and_sync` function can now take in a collection of `Model` or a single `Model` instance.
1212
- Updated documentation.
1313
- Fixing `ForeignKeyColumn` bugs.
1414
- Adding the `alias` as an argument to `Include` class so that developers can flexibly use their own alias for eager model inclusion rather than letting `dataloom` decide for them.
15+
- Adding the `junction_table` as an argument to the `Include` so that we can use this table as a reference for `N-N` associations.
1516
- Introducing self relations
1617

1718
- now you can define self relations in `dataloom`
@@ -65,6 +66,17 @@ We have release the new `dataloom` Version `2.6.0` (`2024-02-27`)
6566
courseId = ForeignKeyColumn(table=Course, type="int")
6667
```
6768

69+
- you can do `eager` data fetching in this type of relationship, however you need to specify the `junction_table`. Here is an example:
70+
71+
```py
72+
english = mysql_loom.find_by_pk(
73+
Course,
74+
pk=engId,
75+
select=["id", "name"],
76+
include=Include(model=Student, junction_table=StudentCourses, has="many"),
77+
)
78+
```
79+
6880
===
6981
Dataloom **`2.3.0`**
7082
===

README.md

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
- [Retrieving Records](#retrieving-records-4)
114114
- [Query Builder.](#query-builder)
115115
- [Why Use Query Builder?](#why-use-query-builder)
116-
- [What is coming next?](#what-is-coming-next)
117116
- [Contributing](#contributing)
118117
- [License](#license)
119118

@@ -623,16 +622,17 @@ posts = pg_loom.find_all(
623622

624623
The `Include` class facilitates eager loading for models with relationships. Below is a table detailing the parameters available for the `Include` class:
625624

626-
| Argument | Description | Type | Default | Required |
627-
| --------- | ---------------------------------------------------------------------------------- | ------------------- | -------- | -------- |
628-
| `model` | The model to be included when eagerly fetching records. | `Model` | - | Yes |
629-
| `order` | The list of order specifications for sorting the included data. | `list[Order]` | `[]` | No |
630-
| `limit` | The maximum number of records to include. | `int \| None` | `0` | No |
631-
| `offset` | The number of records to skip before including. | `int \| None` | `0` | No |
632-
| `select` | The list of columns to include. | `list[str] \| None` | `None` | No |
633-
| `has` | The relationship type between the current model and the included model. | `INCLUDE_LITERAL` | `"many"` | No |
634-
| `include` | The extra included models. | `list[Include]` | `[]` | No |
635-
| `alias` | The alias name for the included model. Very important when mapping self relations. | `str` | `None` | No |
625+
| Argument | Description | Type | Default | Required |
626+
| ---------------- | ------------------------------------------------------------------------------------------- | ------------------- | -------- | -------- |
627+
| `model` | The model to be included when eagerly fetching records. | `Model` | - | Yes |
628+
| `junction_table` | The `junction_table` model that is used as a reference table in a many to many association. | `Model` | `None` | No |
629+
| `order` | The list of order specifications for sorting the included data. | `list[Order]` | `[]` | No |
630+
| `limit` | The maximum number of records to include. | `int \| None` | `0` | No |
631+
| `offset` | The number of records to skip before including. | `int \| None` | `0` | No |
632+
| `select` | The list of columns to include. | `list[str] \| None` | `None` | No |
633+
| `has` | The relationship type between the current model and the included model. | `INCLUDE_LITERAL` | `"many"` | No |
634+
| `include` | The extra included models. | `list[Include]` | `[]` | No |
635+
| `alias` | The alias name for the included model. Very important when mapping self relations. | `str` | `None` | No |
636636

637637
#### `Group` Class
638638

@@ -2339,53 +2339,74 @@ mysql_loom.insert_bulk(
23392339

23402340
##### Retrieving Records
23412341

2342-
Now let's query employee `Michael` with his supervisor.
2342+
Now let's query a student called `Alice` with her courses. We can do it as follows:
23432343

23442344
```py
2345-
emp = mysql_loom.find_by_pk(
2346-
instance=Employee, pk=2, select=["id", "name", "supervisorId"]
2345+
s = mysql_loom.find_by_pk(
2346+
Student,
2347+
pk=stud1,
2348+
select=["id", "name"],
23472349
)
2348-
sup = mysql_loom.find_by_pk(
2349-
instance=Employee, select=["id", "name"], pk=emp["supervisorId"]
2350+
c = mysql_loom.find_many(
2351+
StudentCourses,
2352+
filters=Filter(column="studentId", value=stud1),
2353+
select=["courseId"],
2354+
)
2355+
courses = mysql_loom.find_many(
2356+
Course,
2357+
filters=Filter(column="id", operator="in", value=[list(i.values())[0] for i in c]),
2358+
select=["id", "name"],
23502359
)
2351-
emp_and_sup = {**emp, "supervisor": sup}
2352-
print(emp_and_sup) # ? = {'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'supervisor': {'id': 1, 'name': 'John Doe'}}
2353-
```
23542360

2355-
We're querying the database to retrieve information about a `employee` and their associated `supervisor`.
2361+
alice = {**s, "courses": courses}
2362+
print(courses) # ? = {'id': 1, 'name': 'Alice', 'courses': [{'id': 1, 'name': 'Mathematics'}, {'id': 2, 'name': 'English'}, {'id': 3, 'name': 'Physics'}]}
2363+
```
23562364

2357-
1. **Querying an Employee**:
2365+
We're querying the database to retrieve information about a `student` and their associated `courses`. Here are the steps in achieving that:
23582366

2359-
- We use `mysql_loom.find_by_pk()` to fetch a single employee record from the database.
2360-
- The employee's ID is specified as `2`.
2367+
1. **Querying Student**:
23612368

2362-
2. **Querying Supervisor**:
2369+
- We use `mysql_loom.find_by_pk()` to fetch a single `student` record from the database in the table `students`.
23632370

2364-
- We use `mysql_loom.find_by_pk()` to retrieve a supervisor that is associated with this employee.
2365-
- We create a dictionary `emp_and_sup` containing the `employee` information and their `supervisor`.
2371+
2. **Querying Course Id's**:
2372+
- Next we are going to query all the course ids of that student and store them in `c` in the joint table `students_courses`.
2373+
- We use `mysql_loom.find_many()` to retrieve the course `ids` of `alice`.
2374+
3. **Querying Course**:
2375+
- Next we will query all the courses using the operator `in` in the `courses` table based on the id's we obtained previously.
23662376

2367-
With eager loading this can be done in one query as follows the above can be done as follows:
2377+
As you can see we are doing a lot of work to get the information about `Alice`. With eager loading this can be done in one query as follows the above can be done as follows:
23682378

23692379
```py
2370-
emp_and_sup = mysql_loom.find_by_pk(
2371-
instance=Employee,
2372-
pk=2,
2373-
select=["id", "name", "supervisorId"],
2380+
alice = mysql_loom.find_by_pk(
2381+
Student,
2382+
pk=stud1,
2383+
select=["id", "name"],
23742384
include=Include(
2375-
model=Employee,
2376-
has="one",
2377-
select=["id", "name"],
2378-
alias="supervisor",
2385+
model=Course, junction_table=StudentCourses, alias="courses", has="many"
23792386
),
23802387
)
23812388

2382-
print(emp_and_sup) # ? = {'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'supervisor': {'id': 1, 'name': 'John Doe'}}
2389+
print(alice) # ? = {'id': 1, 'name': 'Alice', 'courses': [{'id': 1, 'name': 'Mathematics'}, {'id': 2, 'name': 'English'}, {'id': 3, 'name': 'Physics'}]}
23832390
```
23842391

2385-
- We use `mysql_loom.find_by_pk()` to fetch a single an employee record from the database.
2386-
- Additionally, we include associated `employee` record using `eager` loading with an `alias` of `supervisor`.
2392+
- We use `mysql_loom.find_by_pk()` to retrieve a single student record from the database.
2393+
- Furthermore, we include the associated `course` records using `eager` loading with an `alias` of `courses`.
2394+
- We specify a `junction_table` in our `Include` statement. This allows dataloom to recognize the relationship between the `students` and `courses` tables through this `junction_table`.
23872395

2388-
> 👍 **Pro Tip:** Note that the `alias` is very important in this situation because it allows you to get the included relationships with objects that are named well, if you don't give an alias dataloom will just use the model class name as the alias of your included models, in this case you will get an object that looks like `{'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'employee': {'id': 1, 'name': 'John Doe'}}`, which practically and theoretically doesn't make sense.
2396+
> 👍 **Pro Tip:** It is crucial to specify the `junction_table` when querying in a many-to-many (`N-N`) relationship. This is because, by default, the models will not establish a direct many-to-many relationship without referencing the `junction_table`. They lack foreign key columns within them to facilitate this relationship.
2397+
2398+
As for our last example let's query all the students that are enrolled in the `English` class. We can easily do it as follows:
2399+
2400+
```py
2401+
english = mysql_loom.find_by_pk(
2402+
Course,
2403+
pk=engId,
2404+
select=["id", "name"],
2405+
include=Include(model=Student, junction_table=StudentCourses, has="many"),
2406+
)
2407+
2408+
print(english) # ? = {'id': 2, 'name': 'English', 'students': [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}, {'id': 3, 'name': 'Lisa'}]}
2409+
```
23892410

23902411
### Query Builder.
23912412

@@ -2458,10 +2479,6 @@ The `run` method takes the following as arguments:
24582479
print(result)
24592480
```
24602481

2461-
### What is coming next?
2462-
2463-
1. N-N associations
2464-
24652482
### Contributing
24662483

24672484
Contributions to `dataloom` are welcome! Feel free to submit bug reports, feature requests, or pull requests on [GitHub](https://github.com/CrispenGari/dataloom).

dataloom/keys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Configuration file for unit testing.
22

33

4-
push = False
4+
push = True
55

66

77
class PgConfig:

dataloom/statements/__init__.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def _get_create_table_command(self) -> str:
160160
dialect=self.dialect,
161161
model=self.model,
162162
)
163-
if len(fks) > 2:
163+
if len(fks) > 2 and len(pks) == 0:
164164
raise TooManyFkException(
165165
f"Reference table '{self.table_name}' can not have more than 2 foreign keys."
166166
)
@@ -176,7 +176,7 @@ def _get_create_table_command(self) -> str:
176176
fields = [*user_fields, *predefined_fields]
177177
fields_name = ", ".join(f for f in [" ".join(field) for field in fields])
178178
if self.dialect == "postgres":
179-
if len(fks) == 2 and len(pks) == 0:
179+
if len(fks) == 2 or len(pks) == 0:
180180
pks, user_fields, fks = get_create_reference_table_params(
181181
dialect=self.dialect,
182182
model=self.model,
@@ -205,7 +205,7 @@ def _get_create_table_command(self) -> str:
205205
)
206206

207207
elif self.dialect == "mysql":
208-
if len(fks) == 2 and len(pks) == 0:
208+
if len(fks) == 2 or len(pks) == 0:
209209
pks, user_fields, fks = get_create_reference_table_params(
210210
dialect=self.dialect,
211211
model=self.model,
@@ -234,7 +234,7 @@ def _get_create_table_command(self) -> str:
234234
)
235235

236236
elif self.dialect == "sqlite":
237-
if len(fks) == 2 and len(pks) == 0:
237+
if len(fks) == 2 or len(pks) == 0:
238238
pks, user_fields, fks = get_create_reference_table_params(
239239
dialect=self.dialect,
240240
model=self.model,
@@ -986,7 +986,10 @@ def _get_alter_table_command(self, old_columns: list[str]) -> str:
986986
).get_alter_table_params
987987

988988
alterations = " ".join(alterations) if self.dialect != "sqlite" else ""
989-
989+
if len(fks) > 2 and len(pks) == 0:
990+
raise TooManyFkException(
991+
f"Reference table '{self.table_name}' can not have more than 2 foreign keys."
992+
)
990993
# do we have a single primary key or not?
991994
if len(pks) == 0 and len(fks) != 2:
992995
raise PkNotDefinedException(
@@ -997,11 +1000,6 @@ def _get_alter_table_command(self, old_columns: list[str]) -> str:
9971000
f"You have defined many field as primary keys which is not allowed. Fields ({', '.join(pks)}) are primary keys."
9981001
)
9991002

1000-
if len(fks) > 2:
1001-
raise TooManyFkException(
1002-
f"Reference table '{self.table_name}' can not have more than 2 foreign keys."
1003-
)
1004-
10051003
if self.dialect == "postgres":
10061004
sql = PgStatements.ALTER_TABLE_COMMAND.format(alterations=alterations)
10071005
elif self.dialect == "mysql":
@@ -1017,7 +1015,10 @@ def _get_alter_table_command(self, old_columns: list[str]) -> str:
10171015
dialect=self.dialect,
10181016
model=self.model,
10191017
)
1020-
1018+
if len(fks) > 2 and len(pks) == 0:
1019+
raise TooManyFkException(
1020+
f"Reference table '{self.table_name}' can not have more than 2 foreign keys."
1021+
)
10211022
if len(pks) == 0 and len(fks) != 2:
10221023
raise PkNotDefinedException(
10231024
f"Your table '{self.table_name}' does not have a primary key column and it is not a reference table."
@@ -1026,10 +1027,7 @@ def _get_alter_table_command(self, old_columns: list[str]) -> str:
10261027
raise TooManyPkException(
10271028
f"You have defined many field as primary keys which is not allowed. Fields ({', '.join(pks)}) are primary keys."
10281029
)
1029-
if len(fks) > 2:
1030-
raise TooManyFkException(
1031-
f"Reference table '{self.table_name}' can not have more than 2 foreign keys."
1032-
)
1030+
10331031
fields = [*user_fields, *predefined_fields]
10341032

10351033
fields_name = ", ".join(f for f in [" ".join(field) for field in fields])

0 commit comments

Comments
 (0)