Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move to github actions and change linters #12

Merged
merged 2 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Publish

on:
push:
branches:
- master

jobs:
publish:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install build tools
run: |
python -m pip install --upgrade pip setuptools wheel

- name: Build package
run: |
python setup.py sdist bdist_wheel

- name: Publish to PyPI
uses: pypa/[email protected]
with:
username: __token__
password: ${{ secrets.PYPI_TOKEN }}
packages-dir: dist
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Django Tests

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
django-version: ['4.0', '4.1', '4.2', '5.0', '5.1']
os-version: [ubuntu-22.04]

runs-on: ${{ matrix.os-version }}

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools packaging
python -m pip install Django==${{ matrix.django-version }} pytest

- name: Run Django tests
run: |
python test/manage.py test
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,3 @@ dmypy.json

# Pyre type checker
.pyre/

20 changes: 20 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- id: detect-private-key

- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
args: [--line-length=120]

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: [--profile=black, -m=3, -l=120]
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
56 changes: 28 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![Build Status](https://app.travis-ci.com/isik-kaplan/django-http-exceptions.svg?token=GLRwtEHQ7cKeAZcq6ZpN&branch=master)](https://app.travis-ci.com/isik-kaplan/django-http-exceptions)
[![codecov](https://codecov.io/gh/isik-kaplan/django-http-exceptions/branch/master/graph/badge.svg)](https://codecov.io/gh/isik-kaplan/django-http-exceptions)
[![codecov](https://codecov.io/gh/isik-kaplan/django-http-exceptions/branch/master/graph/badge.svg)](https://codecov.io/gh/isik-kaplan/django-http-exceptions)
[![Python 3.5+](https://img.shields.io/badge/python-3.5+-brightgreen.svg)](#)
[![Django 2.0+](https://img.shields.io/badge/django-2.0+-brightgreen.svg)](#)
[![PyPI - License](https://img.shields.io/pypi/l/django-http-exceptions.svg)](https://pypi.org/project/django-http-exceptions/)
Expand All @@ -14,7 +14,7 @@ It is raisable exceptions for your django views.

## What is it good for?

It makes this
It makes this

````py
def some_function():
Expand All @@ -34,11 +34,11 @@ def some_function():
raise HTTPExceptions.FORBIDDEN # HTTPExceptions.from_status(403)

def view(request):
return some_function()
return some_function()

````

meaning that is saves you from boilerplate code.
meaning that is saves you from boilerplate code.

It also allows you to hook default views to **all possible http response codes**, meaning that you can use more than the 5-6 django provided error handlers.

Expand All @@ -61,66 +61,66 @@ And that is it, you are ready to raise your http exceptions.



## What else?
## What else?


#### `HTTPExceptions`
Base class that provides all the exceptions to be raised.


#### `HTTPExceptions.from_status(status)`
In case you don't want to write
`HTTPExceptions.REQUEST_HEADER_FIELDS_TOO_LARGE`
You can just write
#### `HTTPExceptions.from_status(status)`
In case you don't want to write
`HTTPExceptions.REQUEST_HEADER_FIELDS_TOO_LARGE`
You can just write
`HTTPExceptions.from_status(431)`


#### `HTTPExceptions.BASE_EXCEPTON`
#### `HTTPExceptions.BASE_EXCEPTON`
The base exception for all http exception

#### `HTTPExceptions.register_base_exception(exception)`
Given that `exception` is a class that inherits from `HTTPException` you can customize the exceptions.
Keep in mind that `HTTPException` is an `Exception` subclass itself.


#### `HTTPExceptions.BASE_EXCEPTION.with_response(response)`
#### `HTTPExceptions.BASE_EXCEPTION.with_response(response)`
This is the method for raising exceptions with a response. You can put any response in this method while raising your
error.

Let's say you have a view named `index`, then this example would return what `index` function would return, but with
status code `410`
status code `410`
`HTTPExceptions.GONE.with_response(index(request))`


#### `HTTPExceptions.BASE_EXCEPTION.with_content(content)`
#### `HTTPExceptions.BASE_EXCEPTION.with_content(content)`
This method allow to raise an **HTTPException** with a custom message (can be either `str` or `bytes`).

For instance, `HTTPExceptions.NOT_FOUND.with_content("The user named 'username' could not be found")`
would return something equivalent to `HttpResponse("The user named 'username' could not be found", status=404)`.

#### `HTTPExceptions.BASE_EXCEPTION.with_json(json_data)`
This method allow to raise an **HTTPException** with a custom json response,
This method allow to raise an **HTTPException** with a custom json response,
`json_data` can be anything that `JsonResponse` accepts.

#### `HTTPExceptions.BASE_EXCEPTION.register_default_view(view)`
#### `HTTPExceptions.BASE_EXCEPTION.register_default_view(view)`
`view` is a function that takes only one argument, `request` when you register a default view to an error class with
`HTTPExceptions.NOT_FOUND.register_defaul_view(view)` when `HTTPExceptions.GONE` is raised it returns the view function,
but again, with `404` status code. If the error has been raised with `.with_response`, that is used instead.
`HTTPExceptions.NOT_FOUND.register_defaul_view(view)` when `HTTPExceptions.GONE` is raised it returns the view function,
but again, with `404` status code. If the error has been raised with `.with_response`, that is used instead.


#### `get_current_request`

This function gets you the current request anywhere in your django application, making it easier for your dynamic error
This function gets you the current request anywhere in your django application, making it easier for your dynamic error
responses to be created, like in the `HTTPExceptions.GONE.with_response(index(request))` example.
#### `ExceptionHandlerMiddleware`


#### `ExceptionHandlerMiddleware`

Just there for to exception handling to work.
#### `ThreadLocalRequestMiddleware`


#### `ThreadLocalRequestMiddleware`

Just there for to `get_current_request` to work.


Expand All @@ -134,7 +134,7 @@ class Subscribe(TemplateView):
template = SUBSCRIBE_TEMPLATE
````


## Avaliable Exceptions
```py
HTTPExceptions.CONTINUE # HTTPExceptions.from_status(100)
Expand Down
13 changes: 6 additions & 7 deletions django_http_exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from ._exceptions import * # Not adding this to __all__, there are 57 variables here
from .exceptions import HTTPExceptions
from ._exceptions import * # Not adding this to __all__, there are 57 variables here
from .middleware import (get_current_request, ExceptionHandlerMiddleware,
ThreadLocalRequestMiddleware)
from .middleware import ExceptionHandlerMiddleware, ThreadLocalRequestMiddleware, get_current_request

__all__ = [
'HTTPExceptions',
'ExceptionHandlerMiddleware',
'ThreadLocalRequestMiddleware',
'get_current_request',
"HTTPExceptions",
"ExceptionHandlerMiddleware",
"ThreadLocalRequestMiddleware",
"get_current_request",
]
65 changes: 25 additions & 40 deletions django_http_exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,19 @@

def _is_dunder(name):
"""Returns True if a __dunder__ name, False otherwise."""
return (name[:2] == name[-2:] == '__' and
name[2:3] != '_' and
name[-3:-2] != '_' and
len(name) > 4)
return name[:2] == name[-2:] == "__" and name[2:3] != "_" and name[-3:-2] != "_" and len(name) > 4


class transform(type):
"""A metaclass to help automatically apply a function to all 3rd party members of a class

@DynamicAttrs."""
"""A metaclass to help automatically apply a function to all 3rd party members of a class"""

def __setattr__(self, key, value):
return super().__setattr__(key, self.__transform__(value))

@staticmethod
def __raise_on_new(klass):
"""We don't want our transform types to be initilizable, they are only there to group
together similiar items."""

def __new__(cls, *a, **kw):
raise TypeError('{} can not be initilazed.'.format(klass))
raise TypeError("{} can not be initilazed.".format(klass))

return __new__

Expand All @@ -38,22 +30,15 @@ def __noop_checks(key, value, classdict):
return True

def __new__(mcs, cls, bases, classdict):
_transform = classdict.get('__transform__', mcs.__noop_transform)
_checks = classdict.get('__checks__', mcs.__noop_checks)
return type.__new__(
mcs,
cls,
bases,
{
**{k: _transform(k, v, classdict) if _checks(k, v, classdict) else v
for k, v in classdict.items()},
'__new__': mcs.__raise_on_new(cls)
}
)
c = classdict
_transform = c.get("__transform__", mcs.__noop_transform)
_checks = c.get("__checks__", mcs.__noop_checks)
transformed_classdict = {k: _transform(k, v, c) if _checks(k, v, c) else v for k, v in c.items()}
new_classdict = {**transformed_classdict, "__new__": mcs.__raise_on_new(cls)}
return type.__new__(mcs, cls, bases, new_classdict)


class HTTPException(Exception):
"""@DynamicAttrs."""
_error_handlers = []

@classmethod
Expand Down Expand Up @@ -90,7 +75,7 @@ def remove_error_handler(cls, handler):

@classmethod
def _has_default_view(cls):
return hasattr(cls, '_default_view')
return hasattr(cls, "_default_view")

@classmethod
def _get_default_view_response(cls, request):
Expand All @@ -104,31 +89,31 @@ def __call__(self, *args):


class HTTPExceptions(metaclass=transform):
"""@DynamicAttrs.""" # PycharmDisableInspection
BASE_EXCEPTION = HTTPException
encapsulated = ['exceptions', 'register_base_exception']
encapsulated = ["exceptions", "register_base_exception"]
exceptions = []

def __transform__(key, value, classdict):
base_exception = classdict.get('BASE_EXCEPTION') or HTTPException
base_exception = classdict.get("BASE_EXCEPTION") or HTTPException
if not issubclass(base_exception, HTTPException):
raise TypeError('BASE_EXCEPTION must be a subclass of HTTPException.')
raise TypeError("BASE_EXCEPTION must be a subclass of HTTPException.")
return type(
value.name,
(base_exception,),
{'__module__': 'HTTPExceptions', 'status': value.value,
'description': value.description}
{"__module__": "HTTPExceptions", "status": value.value, "description": value.description},
)

def __checks__(key, value, classdict):
return all((
not _is_dunder(key),
not callable(value),
not isinstance(value, Exception),
not isinstance(value, classmethod),
key != 'encapsulated',
key not in classdict.get('encapsulated', []),
))
return all(
(
not _is_dunder(key),
not callable(value),
not isinstance(value, Exception),
not isinstance(value, classmethod),
key != "encapsulated",
key not in classdict.get("encapsulated", []),
)
)

@classmethod
def from_status(cls, code):
Expand All @@ -139,7 +124,7 @@ def from_status(cls, code):
def register_base_exception(cls, new_exception):
for exception in cls.exceptions:
if not issubclass(new_exception, HTTPException):
raise TypeError('New exception must be a subclass of HTTPException.')
raise TypeError("New exception must be a subclass of HTTPException.")
getattr(cls, exception).__bases__ = (new_exception,)

# Add all possible HTTP status codes from http.HTTPStatus
Expand Down
Loading
Loading