Skip to content

Commit

Permalink
migration to poetry
Browse files Browse the repository at this point in the history
  • Loading branch information
enzofrnt committed Sep 25, 2024
1 parent e39ea65 commit 0118931
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 115 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,6 @@ cython_debug/
# MacOS
.DS_Store
.AppleDouble
.LSOverride
.LSOverride

junit.xml
81 changes: 81 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-toml
- id: check-yaml
files: \.yaml$
- id: trailing-whitespace
exclude: (migrations/|tests/).*
- id: end-of-file-fixer
exclude: (migrations/|tests/).*
- id: check-added-large-files
exclude: (migrations/|tests/).*
- id: check-case-conflict
exclude: (migrations/|tests/).*
- id: check-merge-conflict
exclude: (migrations/|tests/).*
- id: check-docstring-first
exclude: (migrations/|tests/).*

- repo: https://github.com/tox-dev/pyproject-fmt
rev: 2.2.1
hooks:
- id: pyproject-fmt

- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.3.1
hooks:
- id: tox-ini-fmt

- repo: https://github.com/asottile/pyupgrade
rev: v3.15.2
hooks:
- id: pyupgrade

- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.10.1
hooks:
- id: isort
exclude: (migrations/|tests/).*

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
args: [ "--config=pyproject.toml" ]
exclude: (migrations/).*

- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
args: [ "-c", "pyproject.toml", "-r", "." ]
additional_dependencies: [ "bandit[toml]" ]
exclude: (migrations/|tests/).*

- repo: local
hooks:
- id: pytest
name: Pytest
entry: poetry run pytest -v
language: system
types: [ python ]
stages: [ commit ]
pass_filenames: false
always_run: true

- id: pylint
name: pylint
entry: poetry run pylint
language: system
types: [ python ]
require_serial: true
args:
- "-rn"
- "-sn"
- "--rcfile=pyproject.toml"
- "--load-plugins=pylint_pytest"

files: ^hybridrouter/
exclude: (migrations/|tests/).*
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"-s",
"./hybridrouter/tests/"
]
}
}
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,23 @@ This configuration will generate URLs for both APIViews and ViewSets, and includ
**HybridRouter**

- `register(prefix, view, basename=None)`

Registers an `APIView` or `ViewSet` with the specified prefix.

- `prefix`: URL prefix for the view or viewset.
- `view`: The `APIView `or `ViewSet` class.
- `basename`: The base name for the view or viewset (optional). If not provided, it will be automatically generated.
- `register_nested_router(prefix, router)`

Registers a nested router under a specific prefix.

- `prefix`: URL prefix under which the nested router will be registered.
- `router`: The DRF router instance to be nested.

**Attributes**

- `include_intermediate_views` (default True)

Controls whether intermediate API views are automatically created for grouped endpoints. When set to True, the router will generate intermediate views that provide a browsable API listing of all endpoints under a common prefix.

**Notes**
Expand Down Expand Up @@ -178,7 +178,7 @@ router = HybridRouter(enable_intermediate_apiviews=True)
![image](./docs/imgs/After_1.png)
![image](./docs/imgs/After_2.png)

This improves the readability and the logic of the browsable API and provides a better user experience.
This improves the readability and the logic of the browsable API and provides a better user experience.

And as you can see that will not interfere with other already existing views. **Here, the `ServerConfigViewSet` is still accessible through the `coucou` endpoint and as not been overridden by an intermediary API view.**

Expand All @@ -201,9 +201,9 @@ Tests automatic conflict resolution when multiple views or viewsets are register
## Notes

- Compatibility

The HybridRouter is designed to work seamlessly with Django REST Framework and is compatible with existing DRF features like schema generation.

- Spectacular Support
Will be added in the future.

Will be added in the future.
2 changes: 1 addition & 1 deletion hybridrouter/apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.apps import AppConfig


class WaitForDbConfig(AppConfig):
class HybridRouterConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "hybridrouter"
50 changes: 33 additions & 17 deletions hybridrouter/hybridrouter.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import OrderedDict
from typing import Optional, Type, Union, overload

from django.urls import include, path, re_path
from django.urls.exceptions import NoReverseMatch
Expand Down Expand Up @@ -50,12 +51,34 @@ def _add_route(self, path_parts, view, basename=None):
node.basename = basename
node.is_viewset = is_viewset

def register(self, prefix, view, basename=None):
@overload
def register(
self, prefix: str, viewset: Type[APIView], basename: Optional[str] = None
) -> None:
...

@overload
def register(
self, prefix: str, viewset: Type[ViewSetMixin], basename: Optional[str] = None
) -> None:
...

def register(
self,
prefix: str,
viewset: Union[Type[APIView], Type[ViewSetMixin]],
basename: Optional[str] = None,
) -> None:
"""
Registers a ViewSet or a view with the specified prefix.
Registers an APIView or ViewSet with the specified prefix.
Args:
prefix (str): URL prefix for the view or viewset.
viewset (Type[APIView] or Type[ViewSetMixin]): The APIView or ViewSet class.
basename (str, optional): The base name for the view or viewset. Defaults to None.
"""
if basename is None:
basename = self.get_default_basename(view)
basename = self.get_default_basename(viewset)
path_parts = prefix.strip("/").split("/")

# Register the information for conflict resolution
Expand All @@ -64,7 +87,7 @@ def register(self, prefix, view, basename=None):
self.basename_registry[basename].append(
{
"prefix": prefix,
"view": view,
"view": viewset,
"basename": basename,
"path_parts": path_parts,
}
Expand Down Expand Up @@ -93,8 +116,9 @@ def _resolve_basename_conflicts(self):
# Conflict detected
prefixes = [reg["prefix"] for reg in registrations]
logger.warning(
f"The basename '{basename}' is used for multiple registrations: {', '.join(prefixes)}. "
"Generating unique basenames."
"The basename '%s' is used for multiple registrations: %s. Generating unique basenames.",
basename,
", ".join(prefixes),
)
# Assign new unique basenames
for idx, reg in enumerate(registrations, start=1):
Expand Down Expand Up @@ -182,13 +206,6 @@ def _get_viewset_urls(self, viewset, prefix, basename):

return urls

def get_routes(self, viewset):
"""
Return the list of routes for a given viewset.
"""
# We can reuse DRF's get_routes method
return super().get_routes(viewset)

def get_method_map(self, viewset, method_map):
"""
Given a viewset and a mapping {http_method: action},
Expand All @@ -201,15 +218,14 @@ def get_method_map(self, viewset, method_map):
bound_methods[http_method] = action
return bound_methods

def get_lookup_regex(self, viewset):
def get_lookup_regex(self, viewset, lookup_prefix=""):
"""
Return the regex pattern for the lookup field.
"""
lookup_field = getattr(viewset, "lookup_field", "pk")
lookup_url_kwarg = getattr(viewset, "lookup_url_kwarg", None) or lookup_field
lookup_value = getattr(viewset, "lookup_value_regex", "[^/.]+")
return "(?P<{lookup_field}>{lookup_value})".format(
lookup_field=lookup_field, lookup_value=lookup_value
)
return f"(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})"

def _get_api_root_view(self, node, prefix):
api_root_dict = OrderedDict()
Expand Down
28 changes: 10 additions & 18 deletions hybridrouter/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import django
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.urls import get_resolver

from .utils import list_urls
Expand All @@ -13,7 +14,7 @@ def recevoir_test_url_resolver(url_resolver):


def pytest_exception_interact(node, call, report):
global test_url_resolver # Rendre la variable globale
global test_url_resolver

print("test_url_resolver lors de l'exception:", test_url_resolver)

Expand All @@ -23,25 +24,16 @@ def pytest_exception_interact(node, call, report):
if test_url_resolver:
all_urls = test_url_resolver
else:
all_urls = get_resolver().url_patterns

def collect_urls(urlpatterns, prefix="http://localhost/"):
urls = []
for pattern in urlpatterns:
if hasattr(pattern, "url_patterns"):
urls.extend(
collect_urls(
pattern.url_patterns, prefix + str(pattern.pattern)
)
)
else:
url = prefix + str(pattern.pattern)
name = pattern.name if pattern.name else "None"
urls.append(f"{url} -> {name}")
return urls
try:
all_urls = get_resolver().url_patterns
# Votre code ici
except ImproperlyConfigured:
return
except ModuleNotFoundError as e:
print(f"Erreur lors de l'accès au résolveur : {e}")
return

urls_list = list_urls(all_urls, prefix="http://localhost/")
# urls_list = collect_urls(all_urls)
urls_text = "\n".join(urls_list)

if hasattr(report, "longrepr"):
Expand Down
1 change: 1 addition & 0 deletions hybridrouter/tests/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
urlpatterns = []
3 changes: 2 additions & 1 deletion hybridrouter/tests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from .models import Item
from .serializers import ItemSerializer


class ItemView(APIView):
def get(self, request):
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
return Response(serializer.data)
6 changes: 4 additions & 2 deletions hybridrouter/tests/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
from .models import Item
from .serializers import ItemSerializer


class ItemViewSet(ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer



class SlugItemViewSet(ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
lookup_field = 'name'
lookup_field = "name"
18 changes: 10 additions & 8 deletions hybridrouter/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import logging


class ColorFormatter(logging.Formatter):
COLOR_MAP = {
'ERROR': '\033[31m', # Rouge
'WARNING': '\033[33m', # Jaune/Orange
'INFO': '\033[32m', # Vert
"ERROR": "\033[31m", # Rouge
"WARNING": "\033[33m", # Jaune/Orange
"INFO": "\033[32m", # Vert
}
RESET = '\033[0m'
RESET = "\033[0m"

def __init__(self, fmt=None, datefmt=None):
super().__init__(fmt, datefmt)
Expand All @@ -24,13 +25,14 @@ def format(self, record):
# Combiner la date non colorée avec le message coloré
return f"{date_str}{colored_message}"


# Configurer le logger 'hybridrouter'
logger = logging.getLogger('hybridrouter')
logger = logging.getLogger("hybridrouter")
logger.setLevel(logging.DEBUG) # Définir le niveau de log souhaité

# Définir le format avec la date
log_format = '[%(asctime)s] %(levelname)s: %(message)s'
date_format = '%d/%b/%Y %H:%M:%S'
log_format = "[%(asctime)s] %(levelname)s: %(message)s"
date_format = "%d/%b/%Y %H:%M:%S"

# Initialiser le ColorFormatter avec le format et le format de date
color_formatter = ColorFormatter(fmt=log_format, datefmt=date_format)
Expand All @@ -40,4 +42,4 @@ def format(self, record):
handler.setFormatter(color_formatter)

# Ajouter le handler au logger
logger.addHandler(handler)
logger.addHandler(handler)
Loading

0 comments on commit 0118931

Please sign in to comment.