Skip to content

Commit 7c85d82

Browse files
committed
@ pallets-eco#26 | Should support route generation configuration for dashifying method names
1 parent dc763f3 commit 7c85d82

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

docs/index.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ view method in your `FlaskView` like this::
513513
`FlaskView` will generate a route like this:
514514

515515
============ ================================
516-
**rule** /some/my_view/
516+
**rule** /root/my_view/
517517
**endpoint** SomeView:my_view
518518
**method** GET
519519
============ ================================
@@ -541,6 +541,25 @@ too. If you were to define another view like this::
541541
parameters, so if you want or need them you'll need to define the
542542
route yourself using the `@route` decorator.
543543

544+
Sometimes that you need to use `my-view` instead of default `my_view` generated route, you can
545+
use `method_dashified` attribute when defining the view class or when registering the view with the
546+
app. For example::
547+
548+
class SomeView(FlaskView):
549+
route_base = "root"
550+
method_dashified = True
551+
552+
def my_view(self):
553+
return "Check out my view!"
554+
555+
`FlaskView` will generate a route like this:
556+
557+
============ ================================
558+
**rule** /root/my-view/
559+
**endpoint** SomeView:my_view
560+
**method** GET
561+
============ ================================
562+
544563

545564
Decorating Tips
546565
---------------

flask_classful.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class FlaskView(object):
5252
route_base = None
5353
route_prefix = None
5454
trailing_slash = True
55+
method_dashified = False # TODO(hoatle): make True as default instead, this is not a compatible change
5556
special_methods = {
5657
"get": ["GET"],
5758
"put": ["PUT"],
@@ -63,7 +64,7 @@ class FlaskView(object):
6364

6465
@classmethod
6566
def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
66-
trailing_slash=None):
67+
trailing_slash=None, method_dashified=None):
6768
"""Registers a FlaskView class for use with a specific instance of a
6869
Flask app. Any methods not prefixes with an underscore are candidates
6970
to be routed and will have routes registered when this method is
@@ -81,6 +82,10 @@ def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
8182
:param route_prefix: A prefix to be applied to all routes registered
8283
for this class. Precedes route_base. Overrides
8384
the class' route_prefix if it has been set.
85+
:param trailing_slash: An option to put trailing slashes at the end of routes
86+
without parameters.
87+
:param method_dashified: An option to dashify method name from some_route to /some-route/
88+
route instead of default /some_route/
8489
"""
8590

8691
if cls is FlaskView:
@@ -104,6 +109,10 @@ def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
104109
cls.orig_trailing_slash = cls.trailing_slash
105110
cls.trailing_slash = trailing_slash
106111

112+
if method_dashified is not None:
113+
cls.orig_method_dashified = cls.method_dashified
114+
cls.method_dashified = method_dashified
115+
107116
members = get_interesting_members(FlaskView, cls)
108117

109118
for name, value in members:
@@ -137,6 +146,8 @@ def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
137146
app.add_url_rule(rule, route_name, proxy, methods=methods, subdomain=subdomain)
138147

139148
else:
149+
if cls.method_dashified is True:
150+
name = _dashify_underscore(name)
140151
route_str = '/{0!s}/'.format(name)
141152
if not cls.trailing_slash:
142153
route_str = route_str.rstrip('/')
@@ -157,6 +168,10 @@ def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
157168
cls.trailing_slash = cls.orig_trailing_slash
158169
del cls.orig_trailing_slash
159170

171+
if hasattr(cls, "orig_method_dashified"):
172+
cls.method_dashified = cls.orig_method_dashified
173+
del cls.orig_method_dashified
174+
160175
@classmethod
161176
def parse_options(cls, options):
162177
"""Extracts subdomain and endpoint values from the options dict and returns
@@ -315,17 +330,11 @@ def get_route_base(cls):
315330

316331
@classmethod
317332
def default_route_base(cls):
318-
first_cap_re = re.compile('(.)([A-Z][a-z]+)')
319-
all_cap_re = re.compile('([a-z0-9])([A-Z])')
320-
321-
def dashify(name): #TODO(hoatle): refactor this
322-
s1 = first_cap_re.sub(r'\1-\2', name)
323-
return all_cap_re.sub(r'\1-\2', s1).lower()
324333

325334
if cls.__name__.endswith("View"):
326-
route_base = dashify(cls.__name__[:-4])
335+
route_base = _dashify_uppercase(cls.__name__[:-4])
327336
else:
328-
route_base = dashify(cls.__name__)
337+
route_base = _dashify_uppercase(cls.__name__)
329338

330339
return route_base
331340

@@ -340,6 +349,18 @@ def build_route_name(cls, method_name):
340349
return cls.__name__ + ":{0!s}".format(method_name)
341350

342351

352+
def _dashify_uppercase(name):
353+
"""convert somethingWithUppercase into something-with-uppercase"""
354+
first_cap_re = re.compile('(.)([A-Z][a-z]+)') # better to define this once
355+
all_cap_re = re.compile('([a-z0-9])([A-Z])')
356+
s1 = first_cap_re.sub(r'\1-\2', name)
357+
return all_cap_re.sub(r'\1-\2', s1).lower()
358+
359+
def _dashify_underscore(name):
360+
"""convert something_with_underscore into something-with-underscore"""
361+
return '-'.join(re.split('_', name))
362+
363+
343364
def get_interesting_members(base_class, cls):
344365
"""Returns a list of methods that can be routed to"""
345366

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import json
2+
from flask import Flask
3+
from flask_classful import FlaskView, route
4+
from nose.tools import eq_
5+
6+
7+
class DashifiedDefaultView(FlaskView):
8+
9+
def some_route(self):
10+
return "some route"
11+
12+
13+
class DashifiedAttributeView(FlaskView):
14+
method_dashified = True
15+
16+
def another_route(self):
17+
return "another route"
18+
19+
class DashifiedAttributeOverrideView(FlaskView):
20+
method_dashified = True
21+
22+
def yet_another_route(self):
23+
return "yet another route"
24+
25+
26+
app = Flask('test-app')
27+
DashifiedDefaultView.register(app, method_dashified=True)
28+
DashifiedAttributeView.register(app)
29+
DashifiedAttributeOverrideView.register(app, method_dashified=False)
30+
client = app.test_client()
31+
32+
33+
def test_original_method_dashifield():
34+
eq_(False, DashifiedDefaultView.method_dashified)
35+
eq_(True, DashifiedAttributeView.method_dashified)
36+
eq_(True, DashifiedAttributeOverrideView.method_dashified)
37+
38+
39+
def test_some_route():
40+
resp = client.get('/dashified-default/some-route/')
41+
eq_(b"some route", resp.data)
42+
43+
44+
def test_another_route():
45+
resp = client.get('/dashified-attribute/another-route/')
46+
eq_(b"another route", resp.data)
47+
48+
49+
def test_yet_another_route():
50+
resp = client.get('/dashified-attribute-override/yet_another_route/')
51+
eq_(b"yet another route", resp.data)

test_classful_py3/test_type_hints.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import sys
2-
31
import json
42
from flask import Flask
53
from flask_classful import FlaskView, route

0 commit comments

Comments
 (0)