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.
- {Plinx-0.0.1 → Plinx-1.0.0}/PKG-INFO +26 -21
- {Plinx-0.0.1 → Plinx-1.0.0}/Plinx.egg-info/PKG-INFO +26 -21
- {Plinx-0.0.1 → Plinx-1.0.0}/Plinx.egg-info/SOURCES.txt +6 -5
- Plinx-1.0.0/Plinx.egg-info/requires.txt +2 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/README.md +25 -20
- Plinx-1.0.0/plinx/orm/__init__.py +1 -0
- Plinx-1.0.0/plinx/orm/orm.py +240 -0
- Plinx-1.0.0/plinx/orm/utils.py +7 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/setup.py +1 -1
- {Plinx-0.0.1 → Plinx-1.0.0}/tests/test_application.py +0 -11
- Plinx-1.0.0/tests/test_orm.py +272 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/LICENSE +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/Plinx.egg-info/dependency_links.txt +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/Plinx.egg-info/top_level.txt +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/__init__.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/applications.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/methods.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/middleware.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/response.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/status_codes.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/plinx/utils.py +0 -0
- {Plinx-0.0.1 → Plinx-1.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: Plinx
|
3
|
-
Version: 0.0
|
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
|

|
23
|
-

|
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
|
-
-
|
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,
|
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
|
81
|
+
Run your app using a WSGI server (like `gunicorn`):
|
65
82
|
|
66
83
|
```bash
|
67
|
-
|
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
|
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
|

|
23
|
-

|
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
|
-
-
|
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,
|
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
|
81
|
+
Run your app using a WSGI server (like `gunicorn`):
|
65
82
|
|
66
83
|
```bash
|
67
|
-
|
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.
|
16
|
-
plinx
|
17
|
-
plinx
|
18
|
-
|
19
|
-
tests/
|
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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Plinx
|
2
2
|
|
3
3
|

|
4
|
-

|
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
|
-
-
|
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,
|
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
|
62
|
+
Run your app using a WSGI server (like `gunicorn`):
|
46
63
|
|
47
64
|
```bash
|
48
|
-
|
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")]
|
@@ -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
|
File without changes
|
File without changes
|