Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…nto 5.x
  • Loading branch information
josephmancuso committed Oct 17, 2024
2 parents 3464a85 + 3c3c73f commit ca388f1
Show file tree
Hide file tree
Showing 43 changed files with 244 additions and 812 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,3 @@ src/masonite.egg-info/*
.vscode
build/
venv4
.python-version
*.whl
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
init:
cp .env-example .env
poetry install
pip install -r requirements.txt
pip install '.[test]'
# Create MySQL Database
# Create Postgres Database
test:
python -m pytest tests
ci:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</p>
<p align="center">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/MasoniteFramework/masonite/pythonapp.yml?branch=develop">
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="Python Version">

<img alt="GitHub release (latest by date including pre-releases)" src="https://img.shields.io/github/v/release/MasoniteFramework/masonite?include_prereleases">
<img src="https://img.shields.io/github/license/MasoniteFramework/masonite.svg" alt="License">
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
Expand Down Expand Up @@ -32,7 +32,7 @@ Have questions or want to talk? Be sure to join the [Masonite Discord Community]

## Getting Started Quickly

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

```bash
pip install masonite
Expand Down
26 changes: 26 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
argon2-cffi
bcrypt>=3.2,<3.3
black
cleo>=0.8.1,<0.9
cryptography>=36,<37
dotty_dict>=1.3.0,<1.40
exceptionite>=2.0,<3
hashids>=1.3,<1.4
hfilesize>=0.1
hupper>=1.10,<1.11
inflection>=0.3,<0.4
jinja2<3.2
masonite-orm>=2,<3
pendulum>=2,<3
pwnedapi
vonage
pytest
python-dotenv>=0.15,<0.16
responses
slackblocks
tldextract>=2.2,<2.3
werkzeug>=2,<3; python_version < '3.8'
werkzeug>=3,<4; python_version >= '3.8'
watchdog>=2,<3
whitenoise>=5.2,<5.3
pyjwt>=2.4,<2.5
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"inflection>=0.3,<0.4",
"exceptionite>=2.2,<3.0",
"pendulum>=2,<3",
"jinja2<3.1.0",
"jinja2<3.2",
"cleo>=0.8.1,<0.9",
"hupper>=1.10,<1.11",
"bcrypt>=3.2,<3.3",
Expand Down Expand Up @@ -145,8 +145,6 @@
"masonite.helpers",
"masonite.input",
"masonite.loader",
"masonite.logging",
"masonite.logging.drivers",
"masonite.mail.drivers",
"masonite.mail",
"masonite.middleware.route",
Expand Down
2 changes: 1 addition & 1 deletion src/masonite/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.20.0"
__version__ = "4.20.1"
117 changes: 77 additions & 40 deletions src/masonite/cache/drivers/RedisDriver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from typing import Any, TYPE_CHECKING

import pendulum as pdlm
if TYPE_CHECKING:
from redis import Redis

Expand All @@ -9,59 +10,61 @@ class RedisDriver:
def __init__(self, application):
self.application = application
self.connection = None
self._internal_cache: "dict|None" = None
self.options = {}
self._internal_cache: dict = None

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

def get_connection(self) -> "Redis":
if self.connection:
return self.connection

try:
from redis import Redis
except ImportError:
raise ModuleNotFoundError(
"Could not find the 'redis' library. Run 'pip install redis' to fix this."
)

if not self.connection:
self.connection = Redis(
**self.options.get("options", {}),
host=self.options.get("host"),
port=self.options.get("port"),
password=self.options.get("password"),
decode_responses=True,
)

# populate the internal cache the first time
# the connection is established
if self._internal_cache is None and self.connection:
self._load_from_store(self.connection)
self.connection = Redis(
**self.options.get("options", {}),
host=self.options.get("host"),
port=self.options.get("port"),
password=self.options.get("password"),
decode_responses=True,
)

return self.connection

def _load_from_store(self, connection: "Redis" = None) -> None:
def _load_from_store(self) -> None:
"""
copy all the "cache" key value pairs for faster access
"""
if not connection:
if self._internal_cache is not None:
return

if self._internal_cache is None:
self._internal_cache = {}
self._internal_cache = {}

cursor = "0"
prefix = self.get_cache_namespace()
while cursor != 0:
cursor, keys = connection.scan(
cursor, keys = self.get_connection().scan(
cursor=cursor, match=prefix + "*", count=100000
)
if keys:
values = connection.mget(*keys)
values = self.get_connection().mget(*keys)
store_data = dict(zip(keys, values))
for key, value in store_data.items():
key = key.replace(prefix, "")
value = self.unpack_value(value)
self._internal_cache.setdefault(key, value)
# we dont load the ttl (expiry)
# because there is an O(N) performance hit
self._internal_cache[key] = {
"value": value,
"expires": None,
}

def get_cache_namespace(self) -> str:
"""
Expand All @@ -72,37 +75,66 @@ def get_cache_namespace(self) -> str:
return f"{namespace}cache:"

def add(self, key: str, value: Any = None) -> Any:
if not value:
if value is None:
return None

self.put(key, value)
return value

def get(self, key: str, default: Any = None, **options) -> Any:
if default and not self.has(key):
self.put(key, default, **options)
return default

return self._internal_cache.get(key)
self._load_from_store()
if not self.has(key):
return default or None

key_expiry = self._internal_cache[key].get("expires", None)
if key_expiry is None:
# the ttl value can also provide info on the
# existence of the key in the store
ttl = self.get_connection().ttl(key)
if ttl == -1:
# key exists but has no set ttl
ttl = self.get_default_timeout()
elif ttl == -2:
# key not found in store
self._internal_cache.pop(key)
return default or None

key_expiry = self._expires_from_ttl(ttl)
self._internal_cache[key]["expires"] = key_expiry

if pdlm.now() > key_expiry:
# the key has expired so remove it from the cache
self._internal_cache.pop(key)
return default or None

# the key has not yet expired
return self._internal_cache.get(key)["value"]

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

time = self.get_expiration_time(seconds)

store_value = value
if isinstance(value, (dict, list, tuple)):
store_value = json.dumps(value)
elif isinstance(value, int):
store_value = str(value)

self._load_from_store()
key_ttl = seconds or self.get_default_timeout()
self.get_connection().set(
f"{self.get_cache_namespace()}{key}", store_value, ex=time
f"{self.get_cache_namespace()}{key}", store_value, ex=key_ttl
)

if not self.has(key):
self._internal_cache.update({key: value})
expires = self._expires_from_ttl(key_ttl)
self._internal_cache.update({
key: {
"value": value,
"expires": expires,
}
})

def has(self, key: str) -> bool:
self._load_from_store()
return key in self._internal_cache

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

def forget(self, key: str) -> None:
if not self.has(key):
return
self.get_connection().delete(f"{self.get_cache_namespace()}{key}")
self._internal_cache.pop(key)

def flush(self) -> None:
return self.get_connection().flushall()
self.get_connection().flushall()
self._internal_cache = None

def get_expiration_time(self, seconds: int) -> int:
if seconds is None:
seconds = 31557600 * 10

return seconds
def get_default_timeout(self) -> int:
# if unset default timeout of cache vars is 1 month
return int(self.options.get("timeout", 60 * 60 * 24 * 30))

def unpack_value(self, value: Any) -> Any:
value = str(value)
if value.isdigit():
return str(value)
return int(value)

try:
return json.loads(value)
except json.decoder.JSONDecodeError:
return value

def _expires_from_ttl(self, ttl: int) -> pdlm.DateTime:
return pdlm.now().add(seconds=ttl)
43 changes: 26 additions & 17 deletions src/masonite/cookies/Cookie.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
from ..configuration import config


class Cookie:
def __init__(
self,
name,
value,
expires=None,
http_only=True,
path="/",
timezone=None,
secure=False,
samesite="Strict",
):
def __init__(self, name, value, **options):
self.options = options

self.name = name
self.value = value
self.http_only = http_only
self.secure = secure
self.expires = expires
self.timezone = timezone
self.samesite = samesite
self.path = path
self.http_only = self.__get_option('http_only', True)
self.secure = self.__get_option('secure', False)
self.expires = self.__get_option('expires', None)
self.timezone = self.__get_option('timezone', None)
self.samesite = self.__get_option('samesite', 'Strict')
self.path = self.__get_option('path', '/')
self.encrypt = self.__get_option('encrypt', True)

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

return response

def __get_option(self, key: str, default: any):
"""
Get cookie options from config/session.py
if option key found in options then it return that
if not found in options then it will fetch from config
if not found in config then use the default value
"""
if key in self.options:
return self.options[key]
else:
cookie = config('session.drivers.cookie')
return cookie[key] if key in cookie else default
3 changes: 2 additions & 1 deletion src/masonite/drivers/queue/DatabaseDriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def consume(self):
getattr(obj, callback)(*args)

except AttributeError:
obj(*args)
if callable(obj):
obj(*args)

self.success(
f"[{job['id']}][{pendulum.now(tz=self.options.get('tz', 'UTC')).to_datetime_string()}] Job Successfully Processed"
Expand Down
5 changes: 0 additions & 5 deletions src/masonite/facades/Log.py

This file was deleted.

24 changes: 0 additions & 24 deletions src/masonite/facades/Log.pyi

This file was deleted.

1 change: 0 additions & 1 deletion src/masonite/facades/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@
from .Cache import Cache
from .RateLimiter import RateLimiter
from .Broadcast import Broadcast
from .Log import Log
Loading

0 comments on commit ca388f1

Please sign in to comment.