Repository: hadpro24/nimba-framework

Issue:
[DM] named_url_path
- default use function name
- set it with name parameters

Comments:

Patch:
diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md
index 13d8137..0541bb4 100644
--- a/docs/tutorial/index.md
+++ b/docs/tutorial/index.md
@@ -55,7 +55,7 @@ from nimba.http import router
 
 @router('/about')
 def about(request):
-	return "<h1> Hello, <h2> Welcom to my app page"
+	return "<h1> Hello, World </h1>"
 ```
 
 Each life is decorated by a road indicating a path
diff --git a/nimba/core/exceptions.py b/nimba/core/exceptions.py
index 8cbe421..a77b14b 100644
--- a/nimba/core/exceptions.py
+++ b/nimba/core/exceptions.py
@@ -13,4 +13,8 @@ class AppNameIncorrect(Exception):
 	pass
 
 class CommandError(Exception):
-	pass
\ No newline at end of file
+	pass
+
+class NoReverseFound(Exception):
+	pass
+	
\ No newline at end of file
diff --git a/nimba/http/__init__.py b/nimba/http/__init__.py
index 417220d..12cc753 100644
--- a/nimba/http/__init__.py
+++ b/nimba/http/__init__.py
@@ -1,8 +1,9 @@
-from .utils import router, render
+from .utils import router, render, path_reverse
 from .utils import ROUTES
 
 all = [
 	'router',
 	'render'
+	'path_reverse',
 	'ROUTES',
 ]
\ No newline at end of file
diff --git a/nimba/http/resolver.py b/nimba/http/resolver.py
index 824f89e..4113ca4 100644
--- a/nimba/http/resolver.py
+++ b/nimba/http/resolver.py
@@ -97,7 +97,4 @@ def is_valid_method(methods:list) -> None:
 	if not isinstance(methods, list) or len(methods) > 2 or len(methods) < 0:
 		raise ImproperlyMethodsConfig('ErrorConfig : methods must be list and use the valid element GET or POST.')
 
-
-def reverse(name_path:str) -> str:
-	pass
 	
\ No newline at end of file
diff --git a/nimba/http/utils.py b/nimba/http/utils.py
index 6cdd0fa..eb06e85 100644
--- a/nimba/http/utils.py
+++ b/nimba/http/utils.py
@@ -4,6 +4,7 @@ import re
 import http.client
 from wsgiref.headers import Headers
 import pathlib
+import urllib.parse
 
 import traceback
 import mimetypes
@@ -29,10 +30,44 @@ from nimba.http.errors import (
 	error_404,
 	error_500
 )
+from nimba.core.exceptions import (
+	NoReverseFound
+)
 
 ROUTES = {}
+REVERSE_ROUTE_INFO = {}
 PROJECT_MASK = 'PROJECT_MASK_PATH'
 
+def path_reverse(name_path:str, args=None, kwargs=None) -> str:
+	if not isinstance(name_path, str) or not re.match(r"^[^\d\W][\w-]*\Z", name_path):
+			raise ValueError("Name path must but a valid identifier name.")
+	args = args or {}
+	kwargs = kwargs or {}
+	if args and kwargs:
+		raise ValueError(("Don't use *args and **kwargs."
+			"*args is for get and **kwargs for post method."))
+	path = REVERSE_ROUTE_INFO.get(name_path)
+	if not path:
+		raise NoReverseFound(f"Reverse for {name_path} not found.")
+	
+	if args:
+		path = path +'?'+ urllib.parse.urlencode(args)
+	else:
+		regex = r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>'
+		url  = re.compile(regex, 0)
+		helper_path = path
+		for match in url.finditer(path):
+			value = kwargs.get(match['parameter'])
+			if not value:
+				raise NoReverseFound((f"Reverse for {name_path} not found. " 
+					"Keyword arguments '%s' not found." % match['parameter']))
+			path = re.sub(
+				helper_path[match.start():match.end()], 
+				str(value), 
+				path
+			)
+	return path	
+
 def load_static(value):
 	return os.path.join('/staticfiles/', value)
 
@@ -58,6 +93,7 @@ def render(template, contexts=None, status=200, charset='utf-8', content_type='t
 	)
 	#load env jinja2
 	contexts['load_static'] = load_static
+	contexts['path_reverse'] = path_reverse
 	with open(path, 'r') as content_file:
 		content = content_file.read()
 		html_render = env.from_string(content)
@@ -75,7 +111,7 @@ def render(template, contexts=None, status=200, charset='utf-8', content_type='t
 	return response
 
 
-def router(path, methods=['GET']):
+def router(path, methods=['GET'], name=None):
 	"""
 		Routing app
 	"""
@@ -87,6 +123,9 @@ def router(path, methods=['GET']):
 		#format url value url
 		new_path, converters = resolve_pattern(path, callback)
 		ROUTES[new_path] = (callback, converters, path, methods)
+
+		# if: pass
+		REVERSE_ROUTE_INFO[name or callback.__name__] = path
 		def application(environ, start_response):
 			request = Request(environ)
 			#authorized
diff --git a/setup.cfg b/setup.cfg
index be7f926..6debe5b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = nimba
-version = 0.0.7
+version = 0.0.8
 description = Nimba is a modern, fast coding, web framework with Python.
 long_description = file: README.rst
 keywords = python, python3, framework, nimba, nimba-solution, web


Test patch:
diff --git a/tests/test_router.py b/tests/test_router.py
index 54baf49..d795bee 100644
--- a/tests/test_router.py
+++ b/tests/test_router.py
@@ -9,8 +9,9 @@ import pytest
 import shutil
 from unittest.mock import patch
 
-from nimba.http import router, render
+from nimba.http import router, render, path_reverse
 from nimba.core.server import Application
+from nimba.core.exceptions import NoReverseFound
 
 from nimba.test.client import TestCase
 
@@ -22,14 +23,19 @@ def about(request):
 		return 'yes'
 	return TEST
 
-@router('/articles')
+@router('/articles', name='articles')
 def articles(request):
 	return TEST
 
-@router('/article/<int:id>')
+@router('/article/<int:id>', name='article')
 def article(request, id):
 	return str(id)
 
+@router('/info', name='info')
+def info(request):
+	name = request.GET.get('name', '')
+	return name
+
 @router('/me')
 def me(request):
 	return render('awesome_app/me.html')
@@ -79,6 +85,53 @@ class TestRouterRender(TestCase):
 		self.assertEqual(200, response['status_code'])
 		self.assertIn("hello, world", response['text'])
 
+	def test_path_reverse_with_function_name(self):
+		url = path_reverse('about')
+		response = self.get(url)
+		self.assertEqual(200, response['status_code'])
+		self.assertEqual(TEST, response['text'])
+
+	def test_path_reverse_with_name(self):
+		url = path_reverse('articles')
+		response = self.get(url)
+		self.assertEqual(200, response['status_code'])
+		self.assertEqual(TEST, response['text'])
+
+	def test_path_reverse_with_name_and_kwargs(self):
+		#error type name path
+		with self.assertRaises(ValueError) as error:
+			path_reverse(57885)
+		self.assertEqual(str(error.exception), "Name path must but a valid identifier name.")
+		#bad name give
+		invalid_path = 'invalid-article'
+		with self.assertRaises(NoReverseFound) as error:
+			path_reverse(invalid_path)
+		self.assertEqual(str(error.exception), f"Reverse for {invalid_path} not found.")
+		#give kwargs and args
+		with self.assertRaises(ValueError) as error:
+			path_reverse('article', kwargs={'id': 5}, args={'name': 'test'})
+		self.assertEqual(str(error.exception), ("Don't use *args and **kwargs."
+			"*args is for get and **kwargs for post method."))
+		#invalid parmas name
+		invalid_params = 'id_wrong'
+		with self.assertRaises(NoReverseFound) as error:
+			path_reverse('article', kwargs={invalid_params: 5})
+		self.assertEqual(str(error.exception), ("Reverse for article not found. " 
+					"Keyword arguments 'id' not found."))
+		#valid
+		_id = 5
+		url = path_reverse('article', kwargs={'id': _id})
+		response = self.get(url)
+		self.assertEqual(200, response['status_code'])
+		self.assertEqual(str(_id), response['text'])
+
+	def test_path_reverse_with_args(self):
+		name = 'Harouna Diallo'
+		url = path_reverse('info', args={'name': name})
+		response = self.get(url)
+		self.assertEqual(200, response['status_code'])
+		self.assertEqual(name, response['text'])
+
 	def tearDown(self):
 		try:
 			shutil.rmtree('tests/templates')