panther 1.3.2__py3-none-any.whl → 1.5.0__py3-none-any.whl
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.
- panther/__init__.py +1 -1
- panther/cli/template.py +2 -0
- panther/configs.py +3 -1
- panther/db/connection.py +9 -6
- panther/db/queries/mongodb_queries.py +50 -67
- panther/db/queries/pantherdb_queries.py +8 -40
- panther/db/queries/queries.py +48 -11
- panther/db/utils.py +4 -4
- panther/logger.py +17 -9
- panther/main.py +4 -3
- panther/middlewares/monitoring.py +3 -3
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/METADATA +4 -3
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/RECORD +17 -17
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/LICENSE +0 -0
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/WHEEL +0 -0
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/entry_points.txt +0 -0
- {panther-1.3.2.dist-info → panther-1.5.0.dist-info}/top_level.txt +0 -0
panther/__init__.py
CHANGED
panther/cli/template.py
CHANGED
panther/configs.py
CHANGED
@@ -15,6 +15,7 @@ class JWTConfig:
|
|
15
15
|
class Config(TypedDict):
|
16
16
|
base_dir: Path
|
17
17
|
monitoring: bool
|
18
|
+
log_queries: bool
|
18
19
|
urls: dict
|
19
20
|
middlewares: list
|
20
21
|
reversed_middlewares: list
|
@@ -28,7 +29,8 @@ class Config(TypedDict):
|
|
28
29
|
|
29
30
|
config: Config = {
|
30
31
|
'base_dir': Path(),
|
31
|
-
'monitoring':
|
32
|
+
'monitoring': False,
|
33
|
+
'log_queries': False,
|
32
34
|
'secret_key': None,
|
33
35
|
'urls': {},
|
34
36
|
'middlewares': [],
|
panther/db/connection.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from redis import Redis
|
2
2
|
from pantherdb import PantherDB
|
3
3
|
from panther.configs import config
|
4
|
+
from panther.cli.utils import error
|
4
5
|
|
5
6
|
|
6
7
|
class Singleton(object):
|
@@ -19,9 +20,8 @@ class DBSession(Singleton):
|
|
19
20
|
if db_url:
|
20
21
|
self._db_name = db_url[:db_url.find(':')]
|
21
22
|
match self._db_name:
|
22
|
-
|
23
|
-
|
24
|
-
# self._create_mongodb_session(db_url)
|
23
|
+
case 'mongodb':
|
24
|
+
self._create_mongodb_session(db_url)
|
25
25
|
case 'pantherdb':
|
26
26
|
self._create_pantherdb_session(db_url[12:])
|
27
27
|
case _:
|
@@ -37,8 +37,12 @@ class DBSession(Singleton):
|
|
37
37
|
return self._db_name
|
38
38
|
|
39
39
|
def _create_mongodb_session(self, db_url: str) -> None:
|
40
|
-
|
41
|
-
|
40
|
+
try:
|
41
|
+
from pymongo import MongoClient
|
42
|
+
from pymongo.database import Database
|
43
|
+
except ImportError:
|
44
|
+
error('No module named "pymongo"\n\nHint: Try to install with "pip install pymongo"')
|
45
|
+
raise ImportError(error)
|
42
46
|
self._client: MongoClient = MongoClient(db_url)
|
43
47
|
self._session: Database = self._client.get_database()
|
44
48
|
|
@@ -56,7 +60,6 @@ class RedisConnection(Singleton, Redis):
|
|
56
60
|
is_connected: bool = False
|
57
61
|
|
58
62
|
def __init__(self, host: str | None = None, port: int | None = None, **kwargs):
|
59
|
-
# TODO: Check redis installed or not
|
60
63
|
if host and port:
|
61
64
|
super().__init__(host=host, port=port, **kwargs)
|
62
65
|
self.is_connected = True
|
@@ -1,102 +1,85 @@
|
|
1
|
-
from typing import
|
2
|
-
|
3
|
-
import bson
|
1
|
+
from typing import Self
|
4
2
|
|
5
3
|
from panther.db.connection import db # NOQA: F401
|
6
|
-
from panther.
|
7
|
-
|
8
|
-
# TODO: Not sure about this bounding
|
9
|
-
T = TypeVar('T', bound='BaseMongoDBQuery')
|
4
|
+
from panther.exceptions import DBException
|
5
|
+
from panther.db.utils import clean_object_id_in_dicts, merge_dicts
|
10
6
|
|
11
7
|
|
12
8
|
class BaseMongoDBQuery:
|
13
9
|
|
14
10
|
@classmethod
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
obj = eval(f'db.session.{cls.__name__}.find_one(_query)')
|
20
|
-
return cls(**obj) if obj else None
|
11
|
+
def _merge(cls, *args) -> dict:
|
12
|
+
clean_object_id_in_dicts(*args)
|
13
|
+
# TODO: Convert "id" to "_id"
|
14
|
+
return merge_dicts(*args)
|
21
15
|
|
16
|
+
# # # # # Find # # # # #
|
22
17
|
@classmethod
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
return eval(f'db.session.{cls.__name__}.count_documents(_query)')
|
18
|
+
def find_one(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
19
|
+
_query = cls._merge(_data, kwargs)
|
20
|
+
if document := eval(f'db.session.{cls.__name__}.find_one(_query)'):
|
21
|
+
return cls(**document)
|
28
22
|
|
29
23
|
@classmethod
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
result = eval(f'db.session.{cls.__name__}.find(_query)')
|
35
|
-
return [cls(**obj) for obj in result]
|
24
|
+
def find(cls, _data: dict = None, /, **kwargs) -> list[Self]:
|
25
|
+
_query = cls._merge(_data, kwargs)
|
26
|
+
documents = eval(f'db.session.{cls.__name__}.find(_query)')
|
27
|
+
return [cls(**document) for document in documents]
|
36
28
|
|
29
|
+
# # # # # Insert # # # # #
|
37
30
|
@classmethod
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return
|
31
|
+
def insert_one(cls, _data: dict = None, **kwargs) -> Self:
|
32
|
+
document = cls._merge(_data, kwargs)
|
33
|
+
document['id'] = eval(f'db.session.{cls.__name__}.insert_one(document)').inserted_id
|
34
|
+
return cls(**document)
|
42
35
|
|
43
|
-
@
|
44
|
-
def
|
36
|
+
@classmethod
|
37
|
+
def insert_many(cls, _data: dict = None, **kwargs) -> Self:
|
38
|
+
raise DBException('insert_many() is not supported while using MongoDB.')
|
39
|
+
|
40
|
+
# # # # # Delete # # # # #
|
41
|
+
def delete(self) -> None:
|
45
42
|
_filter = {'_id': self._id}
|
46
|
-
|
47
|
-
return bool(result.deleted_count)
|
43
|
+
eval(f'db.session.{self.__class__.__name__}.delete_one(_filter)')
|
48
44
|
|
49
45
|
@classmethod
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
result = eval(f'db.session.{cls.__name__}.delete_one(kwargs)')
|
46
|
+
def delete_one(cls, _data: dict = None, /, **kwargs) -> bool:
|
47
|
+
_query = cls._merge(_data, kwargs)
|
48
|
+
result = eval(f'db.session.{cls.__name__}.delete_one(_query)')
|
54
49
|
return bool(result.deleted_count)
|
55
50
|
|
56
51
|
@classmethod
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
result = eval(f'db.session.{cls.__name__}.delete_many(kwargs)')
|
52
|
+
def delete_many(cls, _data: dict = None, /, **kwargs) -> int:
|
53
|
+
_query = cls._merge(_data, kwargs)
|
54
|
+
result = eval(f'db.session.{cls.__name__}.delete_many(_query)')
|
61
55
|
return result.deleted_count
|
62
56
|
|
63
|
-
|
64
|
-
def update(self,
|
65
|
-
for field, value in
|
66
|
-
|
67
|
-
setattr(self, field, value)
|
57
|
+
# # # # # Update # # # # #
|
58
|
+
def update(self, **kwargs) -> None:
|
59
|
+
for field, value in kwargs.items():
|
60
|
+
setattr(self, field, value)
|
68
61
|
_filter = {'_id': self._id}
|
69
62
|
_update = {'$set': kwargs}
|
70
|
-
|
63
|
+
eval(f'db.session.{self.__class__.__name__}.update_one(_filter, _update)')
|
71
64
|
|
72
65
|
@classmethod
|
73
|
-
|
74
|
-
def update_one(cls, _filter, _data: dict = None, /, **kwargs) -> dict:
|
66
|
+
def update_one(cls, _filter, _data: dict = None, /, **kwargs) -> bool:
|
75
67
|
clean_object_id_in_dicts(_filter)
|
76
|
-
|
77
68
|
_update = {'$set': kwargs | {}}
|
78
69
|
if isinstance(_data, dict):
|
79
70
|
_data['$set'] = _data.get('$set', {}) | (kwargs or {})
|
80
71
|
|
81
|
-
|
82
|
-
|
83
|
-
@classmethod
|
84
|
-
@query_logger
|
85
|
-
def update_many(cls, _filter, **kwargs) -> dict:
|
86
|
-
_update = {'$set': kwargs}
|
87
|
-
return eval(f'db.session.{cls.__name__}.update_many(_filter, _update)')
|
72
|
+
result = eval(f'db.session.{cls.__name__}.update_one(_filter, _data | _update)')
|
73
|
+
return bool(result.updated_count)
|
88
74
|
|
89
75
|
@classmethod
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
return
|
76
|
+
def update_many(cls, _filter, _data: dict = None, /, **kwargs) -> int:
|
77
|
+
_update = {'$set': cls._merge(_data, kwargs)}
|
78
|
+
result = eval(f'db.session.{cls.__name__}.update_many(_filter, _update)')
|
79
|
+
return result.updated_count
|
94
80
|
|
81
|
+
# # # # # Other # # # # #
|
95
82
|
@classmethod
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
if obj:
|
100
|
-
return False, obj
|
101
|
-
else:
|
102
|
-
return True, cls.create(**kwargs)
|
83
|
+
def count(cls, _data: dict = None, /, **kwargs) -> int:
|
84
|
+
_query = cls._merge(_data, kwargs)
|
85
|
+
return eval(f'db.session.{cls.__name__}.count_documents(_query)')
|
@@ -1,43 +1,21 @@
|
|
1
|
-
import operator
|
2
|
-
from functools import reduce
|
3
1
|
from typing import Self
|
4
|
-
from pydantic import ValidationError
|
5
2
|
|
6
3
|
from panther.db.connection import db
|
4
|
+
from panther.db.utils import merge_dicts
|
7
5
|
from panther.exceptions import DBException
|
8
6
|
|
9
7
|
|
10
8
|
class BasePantherDBQuery:
|
11
9
|
|
12
|
-
@classmethod
|
13
|
-
def validate_data(cls, data: dict, is_updating: bool = False) -> Self | None:
|
14
|
-
"""
|
15
|
-
*. Validate the input of user with its class
|
16
|
-
*. If is_updating is True & exception happens but the message was empty
|
17
|
-
Then return nothing (we don't need the obj on update())
|
18
|
-
& we don't have access to the obj after exception ...
|
19
|
-
"""
|
20
|
-
try:
|
21
|
-
obj = cls(**data)
|
22
|
-
except ValidationError as validation_error:
|
23
|
-
error = ', '.join(
|
24
|
-
'{field}="{error}"'.format(field=e['loc'][0], error=e['msg'])
|
25
|
-
for e in validation_error.errors() if not is_updating or e['type'] != 'value_error.missing')
|
26
|
-
if error:
|
27
|
-
message = f'{cls.__name__}({error})'
|
28
|
-
raise DBException(message)
|
29
|
-
else:
|
30
|
-
return obj
|
31
|
-
|
32
10
|
@classmethod
|
33
11
|
def _merge(cls, *args) -> dict:
|
34
12
|
# TODO: Convert "id" to "_id"
|
35
|
-
return
|
13
|
+
return merge_dicts(*args)
|
36
14
|
|
37
15
|
# # # # # Find # # # # #
|
38
16
|
@classmethod
|
39
17
|
def find_one(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
40
|
-
if document := db.session.collection(cls.__name__).
|
18
|
+
if document := db.session.collection(cls.__name__).find_one(**cls._merge(_data, kwargs)):
|
41
19
|
return cls(**document)
|
42
20
|
|
43
21
|
@classmethod
|
@@ -48,13 +26,12 @@ class BasePantherDBQuery:
|
|
48
26
|
# # # # # Insert # # # # #
|
49
27
|
@classmethod
|
50
28
|
def insert_one(cls, _data: dict = None, **kwargs) -> Self:
|
51
|
-
|
52
|
-
|
53
|
-
return obj
|
29
|
+
document = db.session.collection(cls.__name__).insert_one(**cls._merge(_data, kwargs))
|
30
|
+
return cls(**document)
|
54
31
|
|
55
32
|
@classmethod
|
56
33
|
def insert_many(cls, _data: dict = None, /, **kwargs):
|
57
|
-
raise DBException('
|
34
|
+
raise DBException('insert_many() is not supported while using PantherDB.')
|
58
35
|
|
59
36
|
# # # # # Delete # # # # #
|
60
37
|
def delete(self) -> None:
|
@@ -70,7 +47,8 @@ class BasePantherDBQuery:
|
|
70
47
|
|
71
48
|
# # # # # Update # # # # #
|
72
49
|
def update(self, **kwargs) -> None:
|
73
|
-
|
50
|
+
for field, value in kwargs.items():
|
51
|
+
setattr(self, field, value)
|
74
52
|
db.session.collection(self.__class__.__name__).update_one({'_id': self.id}, **kwargs)
|
75
53
|
|
76
54
|
@classmethod
|
@@ -82,16 +60,6 @@ class BasePantherDBQuery:
|
|
82
60
|
return db.session.collection(cls.__name__).update_many(_filter, **cls._merge(_data, kwargs))
|
83
61
|
|
84
62
|
# # # # # Other # # # # #
|
85
|
-
@classmethod
|
86
|
-
def first(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
87
|
-
if document := db.session.collection(cls.__name__).first(**cls._merge(_data, kwargs)):
|
88
|
-
return cls(**document)
|
89
|
-
|
90
|
-
@classmethod
|
91
|
-
def last(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
92
|
-
if document := db.session.collection(cls.__name__).last(**cls._merge(_data, kwargs)):
|
93
|
-
return cls(**document)
|
94
|
-
|
95
63
|
@classmethod
|
96
64
|
def count(cls, _data: dict = None, /, **kwargs) -> int:
|
97
65
|
return db.session.collection(cls.__name__).count(**cls._merge(_data, kwargs))
|
panther/db/queries/queries.py
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
from typing import Self
|
2
2
|
|
3
|
+
from pydantic import ValidationError
|
4
|
+
|
5
|
+
from panther.configs import config
|
6
|
+
from panther.db.utils import log_query
|
3
7
|
from panther.db.queries.mongodb_queries import BaseMongoDBQuery
|
4
8
|
from panther.db.queries.pantherdb_queries import BasePantherDBQuery
|
5
|
-
from panther.
|
9
|
+
from panther.exceptions import DBException
|
6
10
|
|
7
11
|
if config['db_engine'] == 'pantherdb':
|
8
12
|
BaseQuery = BasePantherDBQuery
|
@@ -16,8 +20,25 @@ __all__ = (
|
|
16
20
|
|
17
21
|
class Query(BaseQuery):
|
18
22
|
|
23
|
+
@classmethod
|
24
|
+
def validate_data(cls, data: dict, is_updating: bool = False) -> Self | None:
|
25
|
+
"""
|
26
|
+
*. Validate the input of user with its class
|
27
|
+
*. If is_updating is True & exception happens but the message was empty
|
28
|
+
"""
|
29
|
+
try:
|
30
|
+
cls(**data)
|
31
|
+
except ValidationError as validation_error:
|
32
|
+
error = ', '.join(
|
33
|
+
'{field}="{error}"'.format(field=e['loc'][0], error=e['msg'])
|
34
|
+
for e in validation_error.errors() if not is_updating or e['type'] != 'value_error.missing')
|
35
|
+
if error:
|
36
|
+
message = f'{cls.__name__}({error})'
|
37
|
+
raise DBException(message)
|
38
|
+
|
19
39
|
# # # # # Find # # # # #
|
20
40
|
@classmethod
|
41
|
+
@log_query
|
21
42
|
def find_one(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
22
43
|
"""
|
23
44
|
example:
|
@@ -27,6 +48,7 @@ class Query(BaseQuery):
|
|
27
48
|
return super().find_one(_data, **kwargs)
|
28
49
|
|
29
50
|
@classmethod
|
51
|
+
@log_query
|
30
52
|
def find(cls, _data: dict = None, /, **kwargs) -> list[Self]:
|
31
53
|
"""
|
32
54
|
example:
|
@@ -37,19 +59,23 @@ class Query(BaseQuery):
|
|
37
59
|
|
38
60
|
# # # # # Insert # # # # #
|
39
61
|
@classmethod
|
62
|
+
@log_query
|
40
63
|
def insert_one(cls, _data: dict = None, /, **kwargs) -> Self:
|
41
64
|
"""
|
42
65
|
example:
|
43
66
|
>>> from example.app.models import User
|
44
67
|
>>> User.insert_one(name='Ali', age=24, ...)
|
45
68
|
"""
|
69
|
+
cls.validate_data(kwargs)
|
46
70
|
return super().insert_one(_data, **kwargs)
|
47
71
|
|
48
72
|
@classmethod
|
73
|
+
@log_query
|
49
74
|
def insert_many(cls, _data: dict = None, **kwargs):
|
50
75
|
return super().insert_many(_data, **kwargs)
|
51
76
|
|
52
77
|
# # # # # Delete # # # # #
|
78
|
+
@log_query
|
53
79
|
def delete(self) -> None:
|
54
80
|
"""
|
55
81
|
example:
|
@@ -60,6 +86,7 @@ class Query(BaseQuery):
|
|
60
86
|
return super().delete()
|
61
87
|
|
62
88
|
@classmethod
|
89
|
+
@log_query
|
63
90
|
def delete_one(cls, **kwargs) -> bool:
|
64
91
|
"""
|
65
92
|
example:
|
@@ -69,6 +96,7 @@ class Query(BaseQuery):
|
|
69
96
|
return super().delete_one(**kwargs)
|
70
97
|
|
71
98
|
@classmethod
|
99
|
+
@log_query
|
72
100
|
def delete_many(cls, **kwargs) -> int:
|
73
101
|
"""
|
74
102
|
example:
|
@@ -78,6 +106,7 @@ class Query(BaseQuery):
|
|
78
106
|
return super().delete_many(**kwargs)
|
79
107
|
|
80
108
|
# # # # # Update # # # # #
|
109
|
+
@log_query
|
81
110
|
def update(self, **kwargs) -> None:
|
82
111
|
"""
|
83
112
|
example:
|
@@ -85,9 +114,11 @@ class Query(BaseQuery):
|
|
85
114
|
>>> user = User.find_one(name='Ali')
|
86
115
|
>>> user.update(name='Saba')
|
87
116
|
"""
|
117
|
+
self.validate_data(kwargs, is_updating=True)
|
88
118
|
return super().update(**kwargs)
|
89
119
|
|
90
120
|
@classmethod
|
121
|
+
@log_query
|
91
122
|
def update_one(cls, _filter, _data: dict = None, /, **kwargs) -> bool:
|
92
123
|
"""
|
93
124
|
example:
|
@@ -98,6 +129,7 @@ class Query(BaseQuery):
|
|
98
129
|
return super().update_one(_filter, _data, **kwargs)
|
99
130
|
|
100
131
|
@classmethod
|
132
|
+
@log_query
|
101
133
|
def update_many(cls, _filter, **kwargs) -> int:
|
102
134
|
"""
|
103
135
|
example:
|
@@ -108,29 +140,34 @@ class Query(BaseQuery):
|
|
108
140
|
|
109
141
|
# # # # # Other # # # # #
|
110
142
|
@classmethod
|
111
|
-
|
143
|
+
@log_query
|
144
|
+
def last(cls, _data: dict = None, /, **kwargs) -> Self | None:
|
112
145
|
"""
|
113
|
-
It works same as find_one()
|
114
146
|
example:
|
115
147
|
>>> from example.app.models import User
|
116
|
-
>>> User.
|
148
|
+
>>> user = User.last(name='Ali')
|
117
149
|
"""
|
118
|
-
return super().
|
150
|
+
return super().last(_data, **kwargs)
|
119
151
|
|
120
152
|
@classmethod
|
121
|
-
|
153
|
+
@log_query
|
154
|
+
def count(cls, _data: dict = None, /, **kwargs) -> int:
|
122
155
|
"""
|
123
156
|
example:
|
124
157
|
>>> from example.app.models import User
|
125
|
-
>>> User.
|
158
|
+
>>> User.count(name='Ali')
|
126
159
|
"""
|
127
|
-
return super().
|
160
|
+
return super().count(_data, **kwargs)
|
128
161
|
|
129
162
|
@classmethod
|
130
|
-
|
163
|
+
@log_query
|
164
|
+
def find_or_insert(cls, **kwargs) -> tuple[bool, any]:
|
131
165
|
"""
|
132
166
|
example:
|
133
167
|
>>> from example.app.models import User
|
134
|
-
>>> User.
|
168
|
+
>>> user = User.find_or_insert(name='Ali')
|
135
169
|
"""
|
136
|
-
|
170
|
+
if obj := cls.find_one(**kwargs):
|
171
|
+
return False, obj
|
172
|
+
else:
|
173
|
+
return True, cls.insert_one(**kwargs)
|
panther/db/utils.py
CHANGED
@@ -4,18 +4,18 @@ from functools import reduce
|
|
4
4
|
from time import perf_counter
|
5
5
|
|
6
6
|
from panther.configs import config
|
7
|
-
from panther.logger import
|
7
|
+
from panther.logger import query_logger
|
8
8
|
|
9
9
|
|
10
|
-
def
|
10
|
+
def log_query(func):
|
11
11
|
def log(*args, **kwargs):
|
12
|
-
if config['
|
12
|
+
if config['log_queries'] is False:
|
13
13
|
return func(*args, **kwargs)
|
14
14
|
start = perf_counter()
|
15
15
|
response = func(*args, **kwargs)
|
16
16
|
end = perf_counter()
|
17
17
|
class_name = args[0].__name__ if hasattr(args[0], '__name__') else args[0].__class__.__name__
|
18
|
-
|
18
|
+
query_logger.info(f'\033[1mQuery -->\033[0m {class_name}.{func.__name__}() --> {(end - start) * 1_000:.2} ms')
|
19
19
|
return response
|
20
20
|
return log
|
21
21
|
|
panther/logger.py
CHANGED
@@ -18,6 +18,7 @@ class LogConfig(BaseModel):
|
|
18
18
|
DEFAULT_LOG_FORMAT: str = '%(levelprefix)s | %(asctime)s | %(message)s'
|
19
19
|
FILE_LOG_FORMAT: str = '%(asctime)s | %(message)s'
|
20
20
|
LOG_LEVEL: str = 'DEBUG'
|
21
|
+
MAX_FILE_SIZE: int = 1024 * 1024 * 100 # 100 MB
|
21
22
|
|
22
23
|
version = 1
|
23
24
|
disable_existing_loggers = False
|
@@ -39,15 +40,21 @@ class LogConfig(BaseModel):
|
|
39
40
|
'formatter': 'file_formatter',
|
40
41
|
'filename': LOGS_DIR / 'monitoring.log',
|
41
42
|
'class': 'logging.handlers.RotatingFileHandler',
|
42
|
-
'maxBytes':
|
43
|
+
'maxBytes': MAX_FILE_SIZE, # 100 MB,
|
44
|
+
'backupCount': 3,
|
45
|
+
},
|
46
|
+
'query_file': {
|
47
|
+
'formatter': 'file_formatter',
|
48
|
+
'filename': LOGS_DIR / 'query.log',
|
49
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
50
|
+
'maxBytes': MAX_FILE_SIZE, # 100 MB,
|
43
51
|
'backupCount': 3,
|
44
52
|
},
|
45
53
|
'file': {
|
46
54
|
'formatter': 'file_formatter',
|
47
|
-
|
48
|
-
'filename': LOGS_DIR / f'{config["base_dir"].name}.log',
|
55
|
+
'filename': LOGS_DIR / f'main.log',
|
49
56
|
'class': 'logging.handlers.RotatingFileHandler',
|
50
|
-
'maxBytes':
|
57
|
+
'maxBytes': MAX_FILE_SIZE, # 100 MB,
|
51
58
|
'backupCount': 3,
|
52
59
|
},
|
53
60
|
'default': {
|
@@ -65,13 +72,14 @@ class LogConfig(BaseModel):
|
|
65
72
|
'handlers': ['monitoring_file'],
|
66
73
|
'level': LOG_LEVEL,
|
67
74
|
},
|
75
|
+
'query': {
|
76
|
+
'handlers': ['default', 'query_file'],
|
77
|
+
'level': LOG_LEVEL,
|
78
|
+
},
|
68
79
|
}
|
69
80
|
|
70
81
|
|
71
82
|
dictConfig(LogConfig().dict())
|
72
83
|
logger = logging.getLogger('panther')
|
73
|
-
|
74
|
-
|
75
|
-
"""
|
76
|
-
[debug, info, warning, error, critical]
|
77
|
-
"""
|
84
|
+
query_logger = logging.getLogger('query')
|
85
|
+
monitoring_logger = logging.getLogger('monitoring')
|
panther/main.py
CHANGED
@@ -4,12 +4,12 @@ from runpy import run_path
|
|
4
4
|
from pydantic.main import ModelMetaclass
|
5
5
|
|
6
6
|
from panther import status
|
7
|
-
from panther.
|
7
|
+
from panther.request import Request
|
8
|
+
from panther.response import Response
|
8
9
|
from panther.exceptions import APIException
|
10
|
+
from panther.configs import JWTConfig, config
|
9
11
|
from panther.middlewares.base import BaseMiddleware
|
10
12
|
from panther.middlewares.monitoring import Middleware as MonitoringMiddleware
|
11
|
-
from panther.request import Request
|
12
|
-
from panther.response import Response
|
13
13
|
from panther.routings import find_endpoint, check_urls, collect_urls, finalize_urls
|
14
14
|
from panther._utils import http_response, import_class, read_body, collect_path_variables
|
15
15
|
|
@@ -117,6 +117,7 @@ class Panther:
|
|
117
117
|
|
118
118
|
# Put Variables In "config"
|
119
119
|
config['monitoring'] = self.settings.get('MONITORING', config['monitoring'])
|
120
|
+
config['log_queries'] = self.settings.get('LOG_QUERIES', config['log_queries'])
|
120
121
|
config['default_cache_exp'] = self.settings.get('DEFAULT_CACHE_EXP', config['default_cache_exp'])
|
121
122
|
config['secret_key'] = self._get_secret_key()
|
122
123
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
from time import perf_counter
|
2
2
|
|
3
|
-
from panther.logger import monitoring
|
4
|
-
from panther.middlewares.base import BaseMiddleware
|
5
3
|
from panther.request import Request
|
4
|
+
from panther.logger import monitoring_logger
|
5
|
+
from panther.middlewares.base import BaseMiddleware
|
6
6
|
|
7
7
|
|
8
8
|
class Middleware(BaseMiddleware):
|
@@ -21,4 +21,4 @@ class Middleware(BaseMiddleware):
|
|
21
21
|
but we should put in middlewares chain later ...
|
22
22
|
"""
|
23
23
|
response_time = (perf_counter() - self.start_time) * 1_000
|
24
|
-
|
24
|
+
monitoring_logger.info(f'{self.log} | {response_time: .3} ms | {status_code}')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: panther
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.5.0
|
4
4
|
Summary: Fast & Friendly, Web Framework For Building Async APIs
|
5
5
|
Home-page: https://github.com/alirn76/panther
|
6
6
|
Author: Ali RajabNezhad
|
@@ -14,7 +14,8 @@ Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
15
15
|
Requires-Dist: pantherdb (>=1.2.0)
|
16
16
|
Requires-Dist: pydantic (>=1.10.5)
|
17
|
-
Requires-Dist:
|
17
|
+
Requires-Dist: watchfiles (>=0.18.1)
|
18
|
+
Requires-Dist: uvicorn[standard]
|
18
19
|
Requires-Dist: bpython (>=0.24)
|
19
20
|
Requires-Dist: bson (>=0.5.10)
|
20
21
|
Requires-Dist: redis (>=4.0.2)
|
@@ -34,7 +35,7 @@ Requires-Dist: pymongo (>=4.3.3) ; extra == 'full'
|
|
34
35
|
---
|
35
36
|
|
36
37
|
### Features
|
37
|
-
- Document-oriented Databases
|
38
|
+
- Document-oriented Databases ODM ([PantherDB](https://pypi.org/project/pantherdb/), MongoDB)
|
38
39
|
- Visual API Monitoring (In Terminal)
|
39
40
|
- Cache APIs (In Memory, In Redis)
|
40
41
|
- Built-in Authentication Classes (Customizable)
|
@@ -1,12 +1,12 @@
|
|
1
|
-
panther/__init__.py,sha256=
|
1
|
+
panther/__init__.py,sha256=qhYoTW71O9z3-iK8zrAILsgYWzJ_tcpZQaqQoqWIbdw,89
|
2
2
|
panther/_utils.py,sha256=Ltev6PCrTWHTWSQcWNfHMWDof1_MXODTAcDaGwflgXo,3667
|
3
3
|
panther/app.py,sha256=tojfQaU8x5pZa7m8XS4m9fR7D5sIe_-d96hD2ccBINo,5469
|
4
4
|
panther/authentications.py,sha256=FCRQL3xqN1Tla2vZsOY8-qXwPhnZBFsujJ-R7CeaW7w,2350
|
5
5
|
panther/caching.py,sha256=qCqb-IaXD1T7VU0AzJVMoXawb366ZYob9kblDUT-7sM,2452
|
6
|
-
panther/configs.py,sha256=
|
6
|
+
panther/configs.py,sha256=bRGRy772Rxjb3RunEBBvfa9LCNCmgcc2TKkfbNTK0GM,975
|
7
7
|
panther/exceptions.py,sha256=c1MeSo7_uwDuqoBcjKTFyXz17uXP5iZ3bM0Sp5Stvhw,769
|
8
|
-
panther/logger.py,sha256=
|
9
|
-
panther/main.py,sha256=
|
8
|
+
panther/logger.py,sha256=1skaZg4w55mTh4pSrCohkGp-7dg62lyEIoFAJKMP5L8,2499
|
9
|
+
panther/main.py,sha256=n9Q55NOrx0KgumQOi8Ra3CzxtYPGWZHMKkX4g44e9LQ,7460
|
10
10
|
panther/request.py,sha256=Ix1podGHCU5NlUCT9Emc79GOITKEw623ldpTw3oZm68,4383
|
11
11
|
panther/response.py,sha256=zzfe8vsPSnydN-_peJTizlnmnawxlNbJFU9zvXppYTk,1025
|
12
12
|
panther/routings.py,sha256=sV3xWksKYb3_95BTDOtSqouroHkB98ExoNkgWT1t7jE,4422
|
@@ -17,24 +17,24 @@ panther/cli/create_command.py,sha256=cUPMzXJnwsa2J1eGetWYUnbEL5GEEe5x-_wG3fA3rLg
|
|
17
17
|
panther/cli/main.py,sha256=TttHOr0jA2rkLxCCtpW3ES9x4dDpq5AS32dMN0VwoW0,891
|
18
18
|
panther/cli/monitor_command.py,sha256=wR6Ti8-2-LuLhfPzdXH77g7xlNQAL5flpBoVwu9qdoE,2458
|
19
19
|
panther/cli/run_command.py,sha256=SdguosH7Orlh-8x8eZK_EWrv8MPnXFKzqxruTHYdHqk,1501
|
20
|
-
panther/cli/template.py,sha256=
|
20
|
+
panther/cli/template.py,sha256=KhehW5nzovQzbW75J1lpAAQcIsqucqU4IXQXpVWBj5M,2317
|
21
21
|
panther/cli/utils.py,sha256=QKQ5_qLWtTjpLY0lZ6yt2vDlc2H0jF6HFe3F-8YNJDg,8225
|
22
22
|
panther/db/__init__.py,sha256=lOgqqRkGMzMSS1WS8D7lTChTr9ikIB5ImRPmucOhCyc,54
|
23
|
-
panther/db/connection.py,sha256=
|
23
|
+
panther/db/connection.py,sha256=E6ZRkDY1b-ns6cYBkSCl-DTYLzbRuOaLYFZvU0fauKs,2207
|
24
24
|
panther/db/models.py,sha256=vnhFtVONqhCkTkFMOSz_rTDmAAxNcegPoxk1p5SljLc,1028
|
25
|
-
panther/db/utils.py,sha256=
|
25
|
+
panther/db/utils.py,sha256=6ndrZ9reaVTqnbFhX8MjOYtcrcPSY7UywAbDquY4eEg,1219
|
26
26
|
panther/db/queries/__init__.py,sha256=BMffHS9RbHE-AUAeT9C5uY3L-hpDh0WGRduDUQ9Kpuc,41
|
27
|
-
panther/db/queries/mongodb_queries.py,sha256=
|
28
|
-
panther/db/queries/pantherdb_queries.py,sha256=
|
29
|
-
panther/db/queries/queries.py,sha256=
|
27
|
+
panther/db/queries/mongodb_queries.py,sha256=uXd81Q2ukJskFDYlNfOiuCCkHuH7URyfZMkD6MUiVkk,3205
|
28
|
+
panther/db/queries/pantherdb_queries.py,sha256=Ee3Kb5WIGQ-ZDeAYFHYx_Y34f-I0RgLT0XYFEof5FLM,2505
|
29
|
+
panther/db/queries/queries.py,sha256=MD65JzBtot-2fKbePVNBF5wymsywXfTm3mJwAE2dkIk,5052
|
30
30
|
panther/middlewares/__init__.py,sha256=7RtHuS-MfybnJc6pcBSGhi9teXNhDsnJ3n7h_cXSkJk,66
|
31
31
|
panther/middlewares/base.py,sha256=Php29ckITeGZm6GfauFG3i61bcsb4qoU8RpPLTqsfls,240
|
32
32
|
panther/middlewares/db.py,sha256=C_PevTIaMykJl0NaaMYEfwE_oLdSLfKW2HR9UoPN1dU,508
|
33
|
-
panther/middlewares/monitoring.py,sha256=
|
33
|
+
panther/middlewares/monitoring.py,sha256=CbGNJ4Ja-m9Wq3Ytv7RFyd-Qj9Ft6cYqBZZncCAxccs,852
|
34
34
|
panther/middlewares/redis.py,sha256=_XIvPAOrVl74UoiA6-Xz8hSfWoeuM2Y1e74xeyvW0I4,1126
|
35
|
-
panther-1.
|
36
|
-
panther-1.
|
37
|
-
panther-1.
|
38
|
-
panther-1.
|
39
|
-
panther-1.
|
40
|
-
panther-1.
|
35
|
+
panther-1.5.0.dist-info/LICENSE,sha256=2aF1hL2aC0zRPjzUkSxJUzZbn2_uLoOkn7DHjzZni-I,1524
|
36
|
+
panther-1.5.0.dist-info/METADATA,sha256=N_WVMfNbFUZfevcMvOxEANoqN03SXZWeBlb65BLgahc,5160
|
37
|
+
panther-1.5.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
38
|
+
panther-1.5.0.dist-info/entry_points.txt,sha256=6GPxYFGuzVfNB4YpHFJvYex6iWah5_tLnirAHwj2Qsg,51
|
39
|
+
panther-1.5.0.dist-info/top_level.txt,sha256=VbBs02JGXTIoHMzsX-eLOk2MCbBZzQbLhWiYpI7xI2g,8
|
40
|
+
panther-1.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|