Skip to content

Commit ca388f1

Browse files
committed
Merge branch '4.0' of https://github.com/MasoniteFramework/masonite into 5.x
2 parents 3464a85 + 3c3c73f commit ca388f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+244
-812
lines changed

.github/workflows/pythonapp.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
strategy:
99
matrix:
10-
python-version: ["3.9", "3.10", "3.11"]
10+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
1111
name: Python ${{ matrix.python-version }}
1212
steps:
1313
- uses: actions/checkout@v3

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,3 @@ src/masonite.egg-info/*
1212
.vscode
1313
build/
1414
venv4
15-
.python-version
16-
*.whl

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
init:
22
cp .env-example .env
33
poetry install
4+
pip install -r requirements.txt
5+
pip install '.[test]'
6+
# Create MySQL Database
7+
# Create Postgres Database
48
test:
59
python -m pytest tests
610
ci:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</p>
55
<p align="center">
66
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/MasoniteFramework/masonite/pythonapp.yml?branch=develop">
7-
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="Python Version">
7+
88
<img alt="GitHub release (latest by date including pre-releases)" src="https://img.shields.io/github/v/release/MasoniteFramework/masonite?include_prereleases">
99
<img src="https://img.shields.io/github/license/MasoniteFramework/masonite.svg" alt="License">
1010
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
@@ -32,7 +32,7 @@ Have questions or want to talk? Be sure to join the [Masonite Discord Community]
3232

3333
## Getting Started Quickly
3434

35-
Create and activate a virtual environment and if you have a working Python 3.7+ installation then getting started is as quick as typing
35+
Create and activate a virtual environment and if you have a working Python <= 3.11 installation then getting started is as quick as typing
3636

3737
```bash
3838
pip install masonite

requirements.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
argon2-cffi
2+
bcrypt>=3.2,<3.3
3+
black
4+
cleo>=0.8.1,<0.9
5+
cryptography>=36,<37
6+
dotty_dict>=1.3.0,<1.40
7+
exceptionite>=2.0,<3
8+
hashids>=1.3,<1.4
9+
hfilesize>=0.1
10+
hupper>=1.10,<1.11
11+
inflection>=0.3,<0.4
12+
jinja2<3.2
13+
masonite-orm>=2,<3
14+
pendulum>=2,<3
15+
pwnedapi
16+
vonage
17+
pytest
18+
python-dotenv>=0.15,<0.16
19+
responses
20+
slackblocks
21+
tldextract>=2.2,<2.3
22+
werkzeug>=2,<3; python_version < '3.8'
23+
werkzeug>=3,<4; python_version >= '3.8'
24+
watchdog>=2,<3
25+
whitenoise>=5.2,<5.3
26+
pyjwt>=2.4,<2.5

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"inflection>=0.3,<0.4",
4343
"exceptionite>=2.2,<3.0",
4444
"pendulum>=2,<3",
45-
"jinja2<3.1.0",
45+
"jinja2<3.2",
4646
"cleo>=0.8.1,<0.9",
4747
"hupper>=1.10,<1.11",
4848
"bcrypt>=3.2,<3.3",
@@ -145,8 +145,6 @@
145145
"masonite.helpers",
146146
"masonite.input",
147147
"masonite.loader",
148-
"masonite.logging",
149-
"masonite.logging.drivers",
150148
"masonite.mail.drivers",
151149
"masonite.mail",
152150
"masonite.middleware.route",

src/masonite/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.20.0"
1+
__version__ = "4.20.1"
Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
from typing import Any, TYPE_CHECKING
33

4+
import pendulum as pdlm
45
if TYPE_CHECKING:
56
from redis import Redis
67

@@ -9,59 +10,61 @@ class RedisDriver:
910
def __init__(self, application):
1011
self.application = application
1112
self.connection = None
12-
self._internal_cache: "dict|None" = None
13+
self.options = {}
14+
self._internal_cache: dict = None
1315

1416
def set_options(self, options: dict) -> "RedisDriver":
1517
self.options = options
1618
return self
1719

1820
def get_connection(self) -> "Redis":
21+
if self.connection:
22+
return self.connection
23+
1924
try:
2025
from redis import Redis
2126
except ImportError:
2227
raise ModuleNotFoundError(
2328
"Could not find the 'redis' library. Run 'pip install redis' to fix this."
2429
)
2530

26-
if not self.connection:
27-
self.connection = Redis(
28-
**self.options.get("options", {}),
29-
host=self.options.get("host"),
30-
port=self.options.get("port"),
31-
password=self.options.get("password"),
32-
decode_responses=True,
33-
)
34-
35-
# populate the internal cache the first time
36-
# the connection is established
37-
if self._internal_cache is None and self.connection:
38-
self._load_from_store(self.connection)
31+
self.connection = Redis(
32+
**self.options.get("options", {}),
33+
host=self.options.get("host"),
34+
port=self.options.get("port"),
35+
password=self.options.get("password"),
36+
decode_responses=True,
37+
)
3938

4039
return self.connection
4140

42-
def _load_from_store(self, connection: "Redis" = None) -> None:
41+
def _load_from_store(self) -> None:
4342
"""
4443
copy all the "cache" key value pairs for faster access
4544
"""
46-
if not connection:
45+
if self._internal_cache is not None:
4746
return
4847

49-
if self._internal_cache is None:
50-
self._internal_cache = {}
48+
self._internal_cache = {}
5149

5250
cursor = "0"
5351
prefix = self.get_cache_namespace()
5452
while cursor != 0:
55-
cursor, keys = connection.scan(
53+
cursor, keys = self.get_connection().scan(
5654
cursor=cursor, match=prefix + "*", count=100000
5755
)
5856
if keys:
59-
values = connection.mget(*keys)
57+
values = self.get_connection().mget(*keys)
6058
store_data = dict(zip(keys, values))
6159
for key, value in store_data.items():
6260
key = key.replace(prefix, "")
6361
value = self.unpack_value(value)
64-
self._internal_cache.setdefault(key, value)
62+
# we dont load the ttl (expiry)
63+
# because there is an O(N) performance hit
64+
self._internal_cache[key] = {
65+
"value": value,
66+
"expires": None,
67+
}
6568

6669
def get_cache_namespace(self) -> str:
6770
"""
@@ -72,37 +75,66 @@ def get_cache_namespace(self) -> str:
7275
return f"{namespace}cache:"
7376

7477
def add(self, key: str, value: Any = None) -> Any:
75-
if not value:
78+
if value is None:
7679
return None
7780

7881
self.put(key, value)
7982
return value
8083

8184
def get(self, key: str, default: Any = None, **options) -> Any:
82-
if default and not self.has(key):
83-
self.put(key, default, **options)
84-
return default
85-
86-
return self._internal_cache.get(key)
85+
self._load_from_store()
86+
if not self.has(key):
87+
return default or None
88+
89+
key_expiry = self._internal_cache[key].get("expires", None)
90+
if key_expiry is None:
91+
# the ttl value can also provide info on the
92+
# existence of the key in the store
93+
ttl = self.get_connection().ttl(key)
94+
if ttl == -1:
95+
# key exists but has no set ttl
96+
ttl = self.get_default_timeout()
97+
elif ttl == -2:
98+
# key not found in store
99+
self._internal_cache.pop(key)
100+
return default or None
101+
102+
key_expiry = self._expires_from_ttl(ttl)
103+
self._internal_cache[key]["expires"] = key_expiry
104+
105+
if pdlm.now() > key_expiry:
106+
# the key has expired so remove it from the cache
107+
self._internal_cache.pop(key)
108+
return default or None
109+
110+
# the key has not yet expired
111+
return self._internal_cache.get(key)["value"]
87112

88113
def put(self, key: str, value: Any = None, seconds: int = None, **options) -> Any:
89114
if not key or value is None:
90115
return None
91116

92-
time = self.get_expiration_time(seconds)
93-
94117
store_value = value
95118
if isinstance(value, (dict, list, tuple)):
96119
store_value = json.dumps(value)
120+
elif isinstance(value, int):
121+
store_value = str(value)
97122

123+
self._load_from_store()
124+
key_ttl = seconds or self.get_default_timeout()
98125
self.get_connection().set(
99-
f"{self.get_cache_namespace()}{key}", store_value, ex=time
126+
f"{self.get_cache_namespace()}{key}", store_value, ex=key_ttl
100127
)
101-
102-
if not self.has(key):
103-
self._internal_cache.update({key: value})
128+
expires = self._expires_from_ttl(key_ttl)
129+
self._internal_cache.update({
130+
key: {
131+
"value": value,
132+
"expires": expires,
133+
}
134+
})
104135

105136
def has(self, key: str) -> bool:
137+
self._load_from_store()
106138
return key in self._internal_cache
107139

108140
def increment(self, key: str, amount: int = 1) -> int:
@@ -126,23 +158,28 @@ def remember(self, key: str, callable):
126158
return self.get(key)
127159

128160
def forget(self, key: str) -> None:
161+
if not self.has(key):
162+
return
129163
self.get_connection().delete(f"{self.get_cache_namespace()}{key}")
130164
self._internal_cache.pop(key)
131165

132166
def flush(self) -> None:
133-
return self.get_connection().flushall()
167+
self.get_connection().flushall()
168+
self._internal_cache = None
134169

135-
def get_expiration_time(self, seconds: int) -> int:
136-
if seconds is None:
137-
seconds = 31557600 * 10
138-
139-
return seconds
170+
def get_default_timeout(self) -> int:
171+
# if unset default timeout of cache vars is 1 month
172+
return int(self.options.get("timeout", 60 * 60 * 24 * 30))
140173

141174
def unpack_value(self, value: Any) -> Any:
142175
value = str(value)
143176
if value.isdigit():
144-
return str(value)
177+
return int(value)
178+
145179
try:
146180
return json.loads(value)
147181
except json.decoder.JSONDecodeError:
148182
return value
183+
184+
def _expires_from_ttl(self, ttl: int) -> pdlm.DateTime:
185+
return pdlm.now().add(seconds=ttl)

src/masonite/cookies/Cookie.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1+
from ..configuration import config
2+
3+
14
class Cookie:
2-
def __init__(
3-
self,
4-
name,
5-
value,
6-
expires=None,
7-
http_only=True,
8-
path="/",
9-
timezone=None,
10-
secure=False,
11-
samesite="Strict",
12-
):
5+
def __init__(self, name, value, **options):
6+
self.options = options
7+
138
self.name = name
149
self.value = value
15-
self.http_only = http_only
16-
self.secure = secure
17-
self.expires = expires
18-
self.timezone = timezone
19-
self.samesite = samesite
20-
self.path = path
10+
self.http_only = self.__get_option('http_only', True)
11+
self.secure = self.__get_option('secure', False)
12+
self.expires = self.__get_option('expires', None)
13+
self.timezone = self.__get_option('timezone', None)
14+
self.samesite = self.__get_option('samesite', 'Strict')
15+
self.path = self.__get_option('path', '/')
16+
self.encrypt = self.__get_option('encrypt', True)
2117

2218
def render(self):
2319
response = f"{self.name}={self.value};"
@@ -36,3 +32,16 @@ def render(self):
3632
response += f"SameSite={self.samesite};"
3733

3834
return response
35+
36+
def __get_option(self, key: str, default: any):
37+
"""
38+
Get cookie options from config/session.py
39+
if option key found in options then it return that
40+
if not found in options then it will fetch from config
41+
if not found in config then use the default value
42+
"""
43+
if key in self.options:
44+
return self.options[key]
45+
else:
46+
cookie = config('session.drivers.cookie')
47+
return cookie[key] if key in cookie else default

src/masonite/drivers/queue/DatabaseDriver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def consume(self):
8989
getattr(obj, callback)(*args)
9090

9191
except AttributeError:
92-
obj(*args)
92+
if callable(obj):
93+
obj(*args)
9394

9495
self.success(
9596
f"[{job['id']}][{pendulum.now(tz=self.options.get('tz', 'UTC')).to_datetime_string()}] Job Successfully Processed"

src/masonite/facades/Log.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/masonite/facades/Log.pyi

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/masonite/facades/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,3 @@
1717
from .Cache import Cache
1818
from .RateLimiter import RateLimiter
1919
from .Broadcast import Broadcast
20-
from .Log import Log

0 commit comments

Comments
 (0)