Plinx 0.0.1__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Plinx
3
- Version: 0.0.1
3
+ Version: 1.0.0
4
4
  Summary: Plinx is an experimental, minimalistic, and extensible web framework and ORM written in Python.
5
5
  Home-page: https://github.com/dhavalsavalia/plinx
6
6
  Author: Dhaval Savalia
@@ -20,7 +20,7 @@ License-File: LICENSE
20
20
  # Plinx
21
21
 
22
22
  ![purpose](https://img.shields.io/badge/purpose-learning-green.svg)
23
- ![PyPI](https://img.shields.io/pypi/v/bumbo.svg)
23
+ ![PyPI](https://img.shields.io/pypi/v/Plinx.svg)
24
24
 
25
25
  **Plinx** is an experimental, minimalistic, and extensible WSGI-based web framework and ORM written in Python.
26
26
  It is designed to be simple, fast, and easy to extend, making it ideal for rapid prototyping and educational purposes.
@@ -29,8 +29,9 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
29
29
 
30
30
  ## Features
31
31
 
32
- - 🚀 Minimal and fast web framework
33
- - 🛣️ Intuitive routing system
32
+ - 🚀 Minimal and fast WSGI web framework
33
+ - 💾 Integrated Object-Relational Mapper (ORM)
34
+ - 🛣️ Intuitive routing system (including parameterized and class-based routes)
34
35
  - 🧩 Extensible middleware support
35
36
  - 🧪 Simple, readable codebase for learning and hacking
36
37
  - 📝 Type hints and modern Python best practices
@@ -39,6 +40,12 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
39
40
 
40
41
  ## Installation
41
42
 
43
+ Install from PyPI:
44
+
45
+ ```bash
46
+ pip install Plinx
47
+ ```
48
+
42
49
  Install directly from the git source:
43
50
 
44
51
  ```bash
@@ -52,19 +59,30 @@ pip install git+https://github.com/dhavalsavalia/plinx.git
52
59
  Create a simple web application in seconds:
53
60
 
54
61
  ```python
62
+ # myapp.py
55
63
  from plinx import Plinx
56
64
 
57
65
  app = Plinx()
58
66
 
59
67
  @app.route("/")
60
68
  def index(request, response):
61
- response.text = "Hello, world!"
69
+ response.text = "Hello, Plinx 1.0.0!"
70
+
71
+ # Example using the ORM (requires database setup)
72
+ # from plinx.orm import Database, Table, Column
73
+ # db = Database("my_database.db")
74
+ # class Item(Table):
75
+ # name = Column(str)
76
+ # count = Column(int)
77
+ # db.create(Item)
78
+ # db.save(Item(name="Example", count=1))
62
79
  ```
63
80
 
64
- Run your app (example, assuming you have an ASGI server like `uvicorn`):
81
+ Run your app using a WSGI server (like `gunicorn`):
65
82
 
66
83
  ```bash
67
- uvicorn myapp:app
84
+ pip install gunicorn
85
+ gunicorn myapp:app
68
86
  ```
69
87
 
70
88
  ## Testing
@@ -77,19 +95,6 @@ pytest --cov=.
77
95
 
78
96
  ---
79
97
 
80
- ## Roadmap
81
-
82
- - [x] Web Framework
83
- - [x] Routing
84
- - [x] Explicit Routing Methods (GET, POST, etc.)
85
- - [x] Parameterized Routes
86
- - [x] Class Based Routes
87
- - [x] Django-like Routes
88
- - [x] Middleware Support
89
- - [ ] ORM
90
-
91
- ---
92
-
93
98
  ## Contributing
94
99
 
95
100
  Contributions are welcome! Please open issues or submit pull requests for improvements, bug fixes, or new features.
@@ -104,7 +109,7 @@ This project is licensed under the MIT License. See [LICENSE](LICENSE) for detai
104
109
 
105
110
  ## Author & Contact
106
111
 
107
- Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
112
+ Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
108
113
  For questions or opportunities, feel free to reach out via [LinkedIn](https://www.linkedin.com/in/dhavalsavalia/) or open an issue.
109
114
 
110
115
  ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Plinx
3
- Version: 0.0.1
3
+ Version: 1.0.0
4
4
  Summary: Plinx is an experimental, minimalistic, and extensible web framework and ORM written in Python.
5
5
  Home-page: https://github.com/dhavalsavalia/plinx
6
6
  Author: Dhaval Savalia
@@ -20,7 +20,7 @@ License-File: LICENSE
20
20
  # Plinx
21
21
 
22
22
  ![purpose](https://img.shields.io/badge/purpose-learning-green.svg)
23
- ![PyPI](https://img.shields.io/pypi/v/bumbo.svg)
23
+ ![PyPI](https://img.shields.io/pypi/v/Plinx.svg)
24
24
 
25
25
  **Plinx** is an experimental, minimalistic, and extensible WSGI-based web framework and ORM written in Python.
26
26
  It is designed to be simple, fast, and easy to extend, making it ideal for rapid prototyping and educational purposes.
@@ -29,8 +29,9 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
29
29
 
30
30
  ## Features
31
31
 
32
- - 🚀 Minimal and fast web framework
33
- - 🛣️ Intuitive routing system
32
+ - 🚀 Minimal and fast WSGI web framework
33
+ - 💾 Integrated Object-Relational Mapper (ORM)
34
+ - 🛣️ Intuitive routing system (including parameterized and class-based routes)
34
35
  - 🧩 Extensible middleware support
35
36
  - 🧪 Simple, readable codebase for learning and hacking
36
37
  - 📝 Type hints and modern Python best practices
@@ -39,6 +40,12 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
39
40
 
40
41
  ## Installation
41
42
 
43
+ Install from PyPI:
44
+
45
+ ```bash
46
+ pip install Plinx
47
+ ```
48
+
42
49
  Install directly from the git source:
43
50
 
44
51
  ```bash
@@ -52,19 +59,30 @@ pip install git+https://github.com/dhavalsavalia/plinx.git
52
59
  Create a simple web application in seconds:
53
60
 
54
61
  ```python
62
+ # myapp.py
55
63
  from plinx import Plinx
56
64
 
57
65
  app = Plinx()
58
66
 
59
67
  @app.route("/")
60
68
  def index(request, response):
61
- response.text = "Hello, world!"
69
+ response.text = "Hello, Plinx 1.0.0!"
70
+
71
+ # Example using the ORM (requires database setup)
72
+ # from plinx.orm import Database, Table, Column
73
+ # db = Database("my_database.db")
74
+ # class Item(Table):
75
+ # name = Column(str)
76
+ # count = Column(int)
77
+ # db.create(Item)
78
+ # db.save(Item(name="Example", count=1))
62
79
  ```
63
80
 
64
- Run your app (example, assuming you have an ASGI server like `uvicorn`):
81
+ Run your app using a WSGI server (like `gunicorn`):
65
82
 
66
83
  ```bash
67
- uvicorn myapp:app
84
+ pip install gunicorn
85
+ gunicorn myapp:app
68
86
  ```
69
87
 
70
88
  ## Testing
@@ -77,19 +95,6 @@ pytest --cov=.
77
95
 
78
96
  ---
79
97
 
80
- ## Roadmap
81
-
82
- - [x] Web Framework
83
- - [x] Routing
84
- - [x] Explicit Routing Methods (GET, POST, etc.)
85
- - [x] Parameterized Routes
86
- - [x] Class Based Routes
87
- - [x] Django-like Routes
88
- - [x] Middleware Support
89
- - [ ] ORM
90
-
91
- ---
92
-
93
98
  ## Contributing
94
99
 
95
100
  Contributions are welcome! Please open issues or submit pull requests for improvements, bug fixes, or new features.
@@ -104,7 +109,7 @@ This project is licensed under the MIT License. See [LICENSE](LICENSE) for detai
104
109
 
105
110
  ## Author & Contact
106
111
 
107
- Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
112
+ Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
108
113
  For questions or opportunities, feel free to reach out via [LinkedIn](https://www.linkedin.com/in/dhavalsavalia/) or open an issue.
109
114
 
110
115
  ---
@@ -4,6 +4,7 @@ setup.py
4
4
  Plinx.egg-info/PKG-INFO
5
5
  Plinx.egg-info/SOURCES.txt
6
6
  Plinx.egg-info/dependency_links.txt
7
+ Plinx.egg-info/requires.txt
7
8
  Plinx.egg-info/top_level.txt
8
9
  plinx/__init__.py
9
10
  plinx/applications.py
@@ -12,8 +13,8 @@ plinx/middleware.py
12
13
  plinx/response.py
13
14
  plinx/status_codes.py
14
15
  plinx/utils.py
15
- plinx.egg-info/PKG-INFO
16
- plinx.egg-info/SOURCES.txt
17
- plinx.egg-info/dependency_links.txt
18
- plinx.egg-info/top_level.txt
19
- tests/test_application.py
16
+ plinx/orm/__init__.py
17
+ plinx/orm/orm.py
18
+ plinx/orm/utils.py
19
+ tests/test_application.py
20
+ tests/test_orm.py
@@ -0,0 +1,2 @@
1
+ webob
2
+ parse
@@ -1,7 +1,7 @@
1
1
  # Plinx
2
2
 
3
3
  ![purpose](https://img.shields.io/badge/purpose-learning-green.svg)
4
- ![PyPI](https://img.shields.io/pypi/v/bumbo.svg)
4
+ ![PyPI](https://img.shields.io/pypi/v/Plinx.svg)
5
5
 
6
6
  **Plinx** is an experimental, minimalistic, and extensible WSGI-based web framework and ORM written in Python.
7
7
  It is designed to be simple, fast, and easy to extend, making it ideal for rapid prototyping and educational purposes.
@@ -10,8 +10,9 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
10
10
 
11
11
  ## Features
12
12
 
13
- - 🚀 Minimal and fast web framework
14
- - 🛣️ Intuitive routing system
13
+ - 🚀 Minimal and fast WSGI web framework
14
+ - 💾 Integrated Object-Relational Mapper (ORM)
15
+ - 🛣️ Intuitive routing system (including parameterized and class-based routes)
15
16
  - 🧩 Extensible middleware support
16
17
  - 🧪 Simple, readable codebase for learning and hacking
17
18
  - 📝 Type hints and modern Python best practices
@@ -20,6 +21,12 @@ It is designed to be simple, fast, and easy to extend, making it ideal for rapid
20
21
 
21
22
  ## Installation
22
23
 
24
+ Install from PyPI:
25
+
26
+ ```bash
27
+ pip install Plinx
28
+ ```
29
+
23
30
  Install directly from the git source:
24
31
 
25
32
  ```bash
@@ -33,19 +40,30 @@ pip install git+https://github.com/dhavalsavalia/plinx.git
33
40
  Create a simple web application in seconds:
34
41
 
35
42
  ```python
43
+ # myapp.py
36
44
  from plinx import Plinx
37
45
 
38
46
  app = Plinx()
39
47
 
40
48
  @app.route("/")
41
49
  def index(request, response):
42
- response.text = "Hello, world!"
50
+ response.text = "Hello, Plinx 1.0.0!"
51
+
52
+ # Example using the ORM (requires database setup)
53
+ # from plinx.orm import Database, Table, Column
54
+ # db = Database("my_database.db")
55
+ # class Item(Table):
56
+ # name = Column(str)
57
+ # count = Column(int)
58
+ # db.create(Item)
59
+ # db.save(Item(name="Example", count=1))
43
60
  ```
44
61
 
45
- Run your app (example, assuming you have an ASGI server like `uvicorn`):
62
+ Run your app using a WSGI server (like `gunicorn`):
46
63
 
47
64
  ```bash
48
- uvicorn myapp:app
65
+ pip install gunicorn
66
+ gunicorn myapp:app
49
67
  ```
50
68
 
51
69
  ## Testing
@@ -58,19 +76,6 @@ pytest --cov=.
58
76
 
59
77
  ---
60
78
 
61
- ## Roadmap
62
-
63
- - [x] Web Framework
64
- - [x] Routing
65
- - [x] Explicit Routing Methods (GET, POST, etc.)
66
- - [x] Parameterized Routes
67
- - [x] Class Based Routes
68
- - [x] Django-like Routes
69
- - [x] Middleware Support
70
- - [ ] ORM
71
-
72
- ---
73
-
74
79
  ## Contributing
75
80
 
76
81
  Contributions are welcome! Please open issues or submit pull requests for improvements, bug fixes, or new features.
@@ -85,7 +90,7 @@ This project is licensed under the MIT License. See [LICENSE](LICENSE) for detai
85
90
 
86
91
  ## Author & Contact
87
92
 
88
- Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
93
+ Created and maintained by [Dhaval Savalia](https://github.com/dhavalsavalia).
89
94
  For questions or opportunities, feel free to reach out via [LinkedIn](https://www.linkedin.com/in/dhavalsavalia/) or open an issue.
90
95
 
91
96
  ---
@@ -0,0 +1 @@
1
+ from .orm import Column, Database, ForeignKey, Table # noqa
@@ -0,0 +1,240 @@
1
+ import inspect
2
+ import sqlite3
3
+ from typing import Generic, TypeVar
4
+
5
+ from .utils import SQLITE_TYPE_MAP
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class Database:
11
+ def __init__(self, path: str):
12
+ self.connection = sqlite3.Connection(path)
13
+
14
+ def create(self, table: "Table"):
15
+ self.connection.execute(table._get_create_sql())
16
+
17
+ def save(self, instance: "Table"):
18
+ sql, values = instance._get_insert_sql()
19
+ cursor = self.connection.execute(sql, values)
20
+ instance._data["id"] = cursor.lastrowid
21
+ self.connection.commit()
22
+
23
+ def all(self, table: "Table"):
24
+ sql, fields = table._get_select_all_sql()
25
+ rows = self.connection.execute(sql).fetchall()
26
+
27
+ result = []
28
+
29
+ for row in rows:
30
+ properties = {}
31
+ for field, value in zip(fields, row):
32
+ if field.endswith("_id"):
33
+ foreign_key = field[:-3]
34
+ foreign_table = getattr(table, foreign_key).table
35
+ properties[foreign_key] = self.get(foreign_table, id=value)
36
+ else:
37
+ properties[field] = value
38
+ result.append(table(**properties))
39
+
40
+ return result
41
+
42
+ def get(self, table: "Table", **kwargs):
43
+ sql, fields, params = table._get_select_where_sql(**kwargs)
44
+ row = self.connection.execute(sql, params).fetchone()
45
+
46
+ if row is None:
47
+ raise Exception(f"{table.__name__} instance with {kwargs} does not exist")
48
+
49
+ properties = {}
50
+
51
+ for field, value in zip(fields, row):
52
+ if field.endswith("_id"):
53
+ foreign_key = field[:-3]
54
+ foreign_table = getattr(table, foreign_key).table
55
+ properties[foreign_key] = self.get(foreign_table, id=value)
56
+ else:
57
+ properties[field] = value
58
+
59
+ return table(**properties)
60
+
61
+ def update(self, instance: "Table"):
62
+ sql, values = instance._get_update_sql()
63
+ self.connection.execute(sql, values)
64
+ self.connection.commit()
65
+
66
+ def delete(self, instance: "Table"):
67
+ sql, values = instance._get_delete_sql()
68
+ self.connection.execute(sql, values)
69
+ self.connection.commit()
70
+
71
+ def close(self):
72
+ if self.connection:
73
+ self.connection.close()
74
+ self.connection = None
75
+
76
+ @property
77
+ def tables(self):
78
+ SELECT_TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table';"
79
+ return [x[0] for x in self.connection.execute(SELECT_TABLES_SQL).fetchall()]
80
+
81
+
82
+ class Column:
83
+ def __init__(self, type: Generic[T]):
84
+ self.type = type
85
+
86
+ @property
87
+ def sql_type(self):
88
+ return SQLITE_TYPE_MAP[self.type]
89
+
90
+
91
+ class ForeignKey:
92
+ def __init__(self, table):
93
+ self.table = table
94
+
95
+
96
+ class Table:
97
+ def __init__(self, **kwargs):
98
+ self._data = {"id": None}
99
+
100
+ for key, value in kwargs.items():
101
+ self._data[key] = value
102
+
103
+ def __getattribute__(self, key):
104
+ """
105
+ Values to be access are in `self._data`
106
+ Accessing without __getattribute__ will return Column or ForeignKey and not the actual value
107
+ """
108
+ # Why use super().__getattribute__ instead of self._data[key]?
109
+ # Because otherwise it will create an infinite loop since __getattribute__ will call itself
110
+ # and will never return the value
111
+ _data = super().__getattribute__("_data")
112
+ if key in _data:
113
+ return _data[key]
114
+ return super().__getattribute__(key)
115
+
116
+ def __setattr__(self, key, value):
117
+ """
118
+ Values to be set are in `self._data`
119
+ """
120
+ super().__setattr__(key, value)
121
+ if key in self._data:
122
+ self._data[key] = value
123
+
124
+ @classmethod
125
+ def _get_create_sql(cls):
126
+ CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS {name} ({fields});"
127
+ fields = [
128
+ "id INTEGER PRIMARY KEY AUTOINCREMENT",
129
+ ]
130
+
131
+ for name, field in inspect.getmembers(cls):
132
+ if isinstance(field, Column):
133
+ fields.append(f"{name} {field.sql_type}")
134
+ elif isinstance(field, ForeignKey):
135
+ fields.append(f"{name}_id INTEGER")
136
+
137
+ fields = ", ".join(fields)
138
+ name = cls.__name__.lower()
139
+ return CREATE_TABLE_SQL.format(name=name, fields=fields)
140
+
141
+ def _get_insert_sql(self):
142
+ INSERT_SQL = "INSERT INTO {name} ({fields}) VALUES ({placeholders});"
143
+
144
+ cls = self.__class__
145
+ fields = []
146
+ placeholders = []
147
+ values = []
148
+
149
+ for name, field in inspect.getmembers(cls):
150
+ if isinstance(field, Column):
151
+ fields.append(name)
152
+ values.append(getattr(self, name))
153
+ placeholders.append("?")
154
+ elif isinstance(field, ForeignKey):
155
+ fields.append(name + "_id")
156
+ values.append(getattr(self, name).id)
157
+ placeholders.append("?")
158
+
159
+ fields = ", ".join(fields)
160
+ placeholders = ", ".join(placeholders)
161
+
162
+ sql = INSERT_SQL.format(
163
+ name=cls.__name__.lower(), fields=fields, placeholders=placeholders
164
+ )
165
+
166
+ return sql, values
167
+
168
+ @classmethod
169
+ def _get_select_all_sql(cls):
170
+ SELECT_ALL_SQL = "SELECT {fields} FROM {name};"
171
+
172
+ fields = ["id"]
173
+
174
+ for name, field in inspect.getmembers(cls):
175
+ if isinstance(field, Column):
176
+ fields.append(name)
177
+ elif isinstance(field, ForeignKey):
178
+ fields.append(name + "_id")
179
+
180
+ return SELECT_ALL_SQL.format(
181
+ fields=", ".join(fields),
182
+ name=cls.__name__.lower(),
183
+ ), fields
184
+
185
+ @classmethod
186
+ def _get_select_where_sql(cls, **kwargs):
187
+ SELECT_WHERE_SQL = "SELECT {fields} FROM {name} WHERE {query};"
188
+
189
+ fields = ["id"]
190
+ query = []
191
+ values = []
192
+
193
+ for name, field in inspect.getmembers(cls):
194
+ if isinstance(field, Column):
195
+ fields.append(name)
196
+ elif isinstance(field, ForeignKey):
197
+ fields.append(name + "_id")
198
+
199
+ for key, value in kwargs.items():
200
+ query.append(f"{key} = ?")
201
+ values.append(value)
202
+
203
+ return (
204
+ SELECT_WHERE_SQL.format(
205
+ fields=", ".join(fields),
206
+ name=cls.__name__.lower(),
207
+ query=", ".join(query),
208
+ ),
209
+ fields,
210
+ values,
211
+ )
212
+
213
+ def _get_update_sql(self):
214
+ UPDATE_SQL = "UPDATE {name} SET {fields} WHERE id = ?;"
215
+
216
+ cls = self.__class__
217
+ fields = []
218
+ values = []
219
+
220
+ for name, field in inspect.getmembers(cls):
221
+ if isinstance(field, Column):
222
+ fields.append(name)
223
+ values.append(getattr(self, name))
224
+ elif isinstance(field, ForeignKey):
225
+ fields.append(name + "_id")
226
+ values.append(getattr(self, name).id)
227
+
228
+ values.append(getattr(self, "id"))
229
+
230
+ return UPDATE_SQL.format(
231
+ name=cls.__name__.lower(),
232
+ fields=", ".join([f"{field} = ?" for field in fields]),
233
+ ), values
234
+
235
+ def _get_delete_sql(self):
236
+ DELETE_SQL = "DELETE FROM {name} WHERE id = ?;"
237
+
238
+ return DELETE_SQL.format(
239
+ name=self.__class__.__name__.lower()
240
+ ), [getattr(self, "id")]
@@ -0,0 +1,7 @@
1
+ SQLITE_TYPE_MAP = {
2
+ int: "INTEGER",
3
+ float: "REAL",
4
+ str: "TEXT",
5
+ bytes: "BLOB",
6
+ bool: "INTEGER"
7
+ }
@@ -15,7 +15,7 @@ AUTHOR = "Dhaval Savalia"
15
15
  REQUIRES_PYTHON = ">=3.11.0"
16
16
  VERSION = None
17
17
 
18
- REQUIRED = []
18
+ REQUIRED = ["webob", "parse"]
19
19
  EXTRAS = {}
20
20
 
21
21
  with open("VERSION", "r") as f:
@@ -1,20 +1,9 @@
1
1
  import pytest
2
2
 
3
- from plinx import Plinx
4
3
  from plinx.methods import HTTPMethods
5
4
  from plinx.middleware import Middleware
6
5
 
7
6
 
8
- @pytest.fixture
9
- def app():
10
- return Plinx()
11
-
12
-
13
- @pytest.fixture
14
- def client(app):
15
- return app.test_session()
16
-
17
-
18
7
  class TestFlaskLikeApplication:
19
8
  def test_app_object(self, app):
20
9
  assert app.routes == {}
@@ -0,0 +1,272 @@
1
+ import sqlite3
2
+
3
+ import pytest
4
+
5
+
6
+ def test_database_connection(db):
7
+ assert db.connection is not None
8
+ assert isinstance(db.connection, sqlite3.Connection)
9
+ assert db.tables == []
10
+
11
+
12
+ def test_define_tables(Author, Book):
13
+ assert Author.name.type is str
14
+ assert Book.author.table is Author
15
+
16
+ assert Author.name.sql_type == "TEXT"
17
+ assert Author.age.sql_type == "INTEGER"
18
+
19
+
20
+ def test_create_tables(db, Author, Book):
21
+ db.create(Author)
22
+ db.create(Book)
23
+
24
+ assert (
25
+ Author._get_create_sql()
26
+ == "CREATE TABLE IF NOT EXISTS author (id INTEGER PRIMARY KEY AUTOINCREMENT, age INTEGER, name TEXT);"
27
+ )
28
+ assert (
29
+ Book._get_create_sql()
30
+ == "CREATE TABLE IF NOT EXISTS book (id INTEGER PRIMARY KEY AUTOINCREMENT, author_id INTEGER, published INTEGER, title TEXT);"
31
+ )
32
+
33
+ for table in ("author", "book"):
34
+ assert table in db.tables
35
+
36
+
37
+ def test_create_author_instance(db, Author):
38
+ db.create(Author)
39
+
40
+ john = Author(name="John Doe", age=35)
41
+
42
+ assert john.name == "John Doe"
43
+ assert john.age == 35
44
+ assert john.id is None
45
+
46
+
47
+ def test_save_author_instances(db, Author):
48
+ db.create(Author)
49
+
50
+ john = Author(name="John Doe", age=23)
51
+ db.save(john)
52
+ assert john._get_insert_sql() == (
53
+ "INSERT INTO author (age, name) VALUES (?, ?);",
54
+ [23, "John Doe"],
55
+ )
56
+ assert john.id == 1
57
+
58
+ man = Author(name="Man Harsh", age=28)
59
+ db.save(man)
60
+ assert man.id == 2
61
+
62
+ vik = Author(name="Vik Star", age=43)
63
+ db.save(vik)
64
+ assert vik.id == 3
65
+
66
+ jack = Author(name="Jack Ma", age=39)
67
+ db.save(jack)
68
+ assert jack.id == 4
69
+
70
+
71
+ def test_save_book_instance(db, Author, Book):
72
+ db.create(Author)
73
+ db.create(Book)
74
+
75
+ john = Author(name="John Doe", age=23)
76
+ db.save(john)
77
+
78
+ book = Book(
79
+ title="Test Book",
80
+ published=True,
81
+ author=john,
82
+ )
83
+ db.save(book)
84
+
85
+ assert book._get_insert_sql() == (
86
+ "INSERT INTO book (author_id, published, title) VALUES (?, ?, ?);",
87
+ [1, True, "Test Book"],
88
+ )
89
+ assert book.id == 1
90
+
91
+
92
+ def test_query_all_authors(db, Author):
93
+ db.create(Author)
94
+ john = Author(name="John Doe", age=23)
95
+ vik = Author(name="Vik Star", age=43)
96
+ db.save(john)
97
+ db.save(vik)
98
+
99
+ authors = db.all(Author)
100
+
101
+ assert Author._get_select_all_sql() == (
102
+ "SELECT id, age, name FROM author;",
103
+ ["id", "age", "name"],
104
+ )
105
+ assert len(authors) == 2
106
+ assert type(authors[0]) is Author
107
+ assert {a.age for a in authors} == {23, 43}
108
+ assert {a.name for a in authors} == {"John Doe", "Vik Star"}
109
+
110
+
111
+ def test_query_all_books(db, Author, Book):
112
+ db.create(Author)
113
+ db.create(Book)
114
+
115
+ john = Author(name="John Doe", age=23)
116
+ db.save(john)
117
+
118
+ book = Book(
119
+ title="Test Book",
120
+ published=True,
121
+ author=john,
122
+ )
123
+ db.save(book)
124
+
125
+ books = db.all(Book)
126
+
127
+ assert Book._get_select_all_sql() == (
128
+ "SELECT id, author_id, published, title FROM book;",
129
+ ["id", "author_id", "published", "title"],
130
+ )
131
+ assert len(books) == 1
132
+ assert type(books[0]) is Book
133
+ assert {b.title for b in books} == {"Test Book"}
134
+ assert books[0].author.name == "John Doe"
135
+ assert books[0].author.age == 23
136
+
137
+
138
+ def test_get_author(db, Author):
139
+ db.create(Author)
140
+ john_srow = Author(name="John Doe", age=43) # get it? John Snow
141
+ db.save(john_srow)
142
+
143
+ john_from_db = db.get(Author, id=1)
144
+
145
+ assert Author._get_select_where_sql(id=1) == (
146
+ "SELECT id, age, name FROM author WHERE id = ?;",
147
+ ["id", "age", "name"],
148
+ [1],
149
+ )
150
+ assert type(john_from_db) is Author
151
+ assert john_from_db.age == 43
152
+ assert john_from_db.name == "John Doe"
153
+ assert john_from_db.id == 1
154
+
155
+
156
+ def test_get_book(db, Author, Book):
157
+ db.create(Author)
158
+ db.create(Book)
159
+
160
+ john = Author(name="John Doe", age=23)
161
+ db.save(john)
162
+
163
+ book = Book(
164
+ title="Test Book",
165
+ published=True,
166
+ author=john,
167
+ )
168
+ db.save(book)
169
+
170
+ book_from_db = db.get(Book, title="Test Book")
171
+ assert Book._get_select_where_sql(title="Test Book") == (
172
+ "SELECT id, author_id, published, title FROM book WHERE title = ?;",
173
+ ["id", "author_id", "published", "title"],
174
+ ["Test Book"],
175
+ )
176
+ assert type(book_from_db) is Book
177
+ assert book_from_db.id == 1
178
+ assert book_from_db.title == "Test Book"
179
+ assert book_from_db.author.name == "John Doe"
180
+ assert book_from_db.author.age == 23
181
+
182
+
183
+ def test_get_invalid_book(db, Author, Book):
184
+ db.create(Author)
185
+ db.create(Book)
186
+
187
+ john = Author(name="John Doe", age=23)
188
+ db.save(john)
189
+
190
+ book = Book(
191
+ title="Test Book",
192
+ published=True,
193
+ author=john,
194
+ )
195
+ db.save(book)
196
+
197
+
198
+ with pytest.raises(Exception) as excinfo:
199
+ db.get(Book, id=2)
200
+
201
+ assert isinstance(excinfo.value, Exception)
202
+ assert "Book instance with {'id': 2} does not exist" == str(excinfo.value)
203
+
204
+
205
+ def test_update_author(db, Author):
206
+ db.create(Author)
207
+ john = Author(name="John Doe", age=23)
208
+ db.save(john)
209
+
210
+ john.age = 43
211
+ john.name = "John Snow"
212
+ db.update(john)
213
+
214
+ assert john._get_update_sql() == (
215
+ "UPDATE author SET age = ?, name = ? WHERE id = ?;",
216
+ [43, "John Snow", 1],
217
+ )
218
+
219
+ john_from_db = db.get(Author, id=john.id)
220
+
221
+ assert john_from_db.age == 43
222
+ assert john_from_db.name == "John Snow"
223
+ assert john_from_db.id == 1
224
+
225
+
226
+ def test_update_book(db, Author, Book):
227
+ db.create(Author)
228
+ db.create(Book)
229
+
230
+ john = Author(name="John Doe", age=23)
231
+ db.save(john)
232
+
233
+ book = Book(
234
+ title="Test Book",
235
+ published=1,
236
+ author=john,
237
+ )
238
+ db.save(book)
239
+
240
+ book.title = "Updated Book"
241
+ book.published = False
242
+ db.update(book)
243
+
244
+ assert book._get_update_sql() == (
245
+ "UPDATE book SET author_id = ?, published = ?, title = ? WHERE id = ?;",
246
+ [1, False, "Updated Book", 1],
247
+ )
248
+
249
+ book_from_db = db.get(Book, id=book.id)
250
+
251
+ assert book_from_db.title == "Updated Book"
252
+ assert book_from_db.id == 1
253
+ assert book_from_db.published == False # noqa 0 is False
254
+
255
+
256
+ def test_delete_author(db, Author):
257
+ db.create(Author)
258
+ john = Author(name="John Doe", age=23)
259
+ db.save(john)
260
+
261
+ assert john._get_delete_sql() == (
262
+ "DELETE FROM author WHERE id = ?;",
263
+ [1]
264
+ )
265
+
266
+ db.delete(john)
267
+
268
+ with pytest.raises(Exception) as excinfo:
269
+ db.get(Author, id=john.id)
270
+
271
+ assert isinstance(excinfo.value, Exception)
272
+ assert "Author instance with {'id': 1} does not exist" == str(excinfo.value)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes