Luke Ross

Scripts

git clone https://lukeross.name/projects/scripts.git/

Small scripts

Commit 0eab2e2cfe760b3b1114bff9f8fba5fd3c464ce9

new package: Werkzeug-FormatStringRouting

Committed 19 Feb 2019 by Luke Ross

Werkzeug-FormatStringRouting/LICENSE

@@ -0,0 +1,32 @@
+Copyright (c) 2018, Luke Ross
+
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.


Werkzeug-FormatStringRouting/README.md

@@ -0,0 +1,56 @@
+# Werkzeug-FormatStringRoutig
+
+This is a replacement for werkzeug.routing.Rule which uses str.format()-style
+routing patterns.
+
+Instead of:
+
+```
+@app.route('/topics/<topic>/edit/<int:n>')
+```
+
+...you can use:
+
+```
+@app.route('/topics/{topic}/edit/{id:n}')
+```
+
+## Using with Flask
+
+Subclass Flask and in your subclass you can instruct Flask to use the
+alternative Rule. Once done, you can instantiate it as normal:
+
+```
+from flask import Flask
+from werkzeug_formatstringrouting import Rule
+
+
+class CustomisedFlask(Flask):
+    url_rule_class = Rule
+
+
+# Use CustomisedFlask where you would use Flask
+app = CustomisedFlask(__name__)
+```
+
+## Quirks & Limitations
+
+- Matches without a type (eg. `{name}`) will not match anything containing
+  slashes, similar to how Werkzeug matches strings.
+
+- To match paths, use the `path` type: `{my_path:path}`.
+
+- For more details on how the matching is handled and the types available,
+  please see the documentation for the `parse` library.
+
+- Use of the `redirect_to` parameter is not supported, because of how Werkzeug
+  builds the target URL to redirect to.
+
+## License
+
+Issued under the same license as Werkzeug. Please see the LICENSE file for
+more details.
+
+## Author
+
+Luke Ross <lr@lukeross.name>


Werkzeug-FormatStringRouting/setup.py

@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+
+from setuptools import setup
+
+setup(
+    name='Werkzeug-FormatStringRouting',
+    version='0.1',
+    url='https://lukeross.name/projects/scripts/',
+    license='BSD',
+    author='Luke Ross',
+    author_email='luke@lukeross.name',
+    description='Very short description',
+    packages=['werkzeug_formatstringrouting'],
+    include_package_data=True,
+    platforms='any',
+    install_requires=[
+        'parse',
+        'Werkzeug',
+    ],
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python :: 3',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+        'Topic :: Software Development :: Libraries :: Python Modules'
+    ]
+)
+


Werkzeug-FormatStringRouting/werkzeug_formatstringrouting/__init__.py

@@ -0,0 +1,117 @@
+import re
+from parse import compile, parse
+from werkzeug import routing
+from werkzeug.urls import url_encode, url_quote
+
+
+def _path(text):
+    return text
+
+
+_path.pattern = routing.PathConverter.regex
+
+
+class Rule(routing.Rule):
+    def compile(self):
+        rule = self.rule if self.is_leaf else self.rule.rstrip('/')
+        self._regex = compile(rule, {
+            'path': _path,
+        })
+
+        self.arguments.update(self._regex._named_fields)
+
+        weight_map = {'path': 200, 'd': 50, 'n': 50}
+        self._argument_weights = [
+            weight_map.get(t, 100)
+            for t in self._regex._name_types.values()
+        ]
+
+        self._static_weights = []
+        self._trace = []
+        for idx, part in enumerate(self.rule.split('/')):
+            if not part:
+                continue
+            self._trace.append(('{' in part, part))
+            if '{' not in part:
+                self._static_weights.append((idx, -len(part)))
+
+    def match(self, path, method=None):
+        domain_value, local_path = path.split('|', 2)
+
+        if not self.is_leaf:
+            local_path = local_path.rstrip('/')
+
+        assert self.map is not None, 'rule not bound'
+
+        if self.map.host_matching:
+            domain_rule = self.host or ''
+        else:
+            domain_rule = self.subdomain or ''
+
+        result = self._regex.parse(local_path)
+        if not result:
+            return
+        domain_result = parse(domain_rule, domain_value)
+        if not domain_result:
+            return
+
+        # Ensure any no-type fields are valid
+        for field_name, field_type in self._regex._name_types.items():
+            if field_type:
+                continue
+            if not re.fullmatch(
+                    routing.BaseConverter.regex, result.named[field_name]):
+                return
+
+        ret = dict()
+        ret.update(domain_result.named)
+        ret.update(result.named)
+
+        if self.defaults:
+            result.update(self.defaults)
+
+        if self.strict_slashes and not self.is_leaf and not path.endswith('/'):
+            raise routing.RequestSlash()
+
+        if self.alias and self.map.redirect_defaults:
+            raise routing.RequestAliasRedirect(ret)
+
+        return ret
+
+    def build(self, values, append_unknown=True):
+        all_values = {}
+        if self.defaults:
+            all_values.update(self.defaults)
+        all_values.update(values)
+
+        if self.map.host_matching:
+            domain_rule = self.host or ''
+        else:
+            domain_rule = self.subdomain or ''
+        domain_rule_parsed = compile(domain_rule)
+
+        try:
+            formatted_url = self.rule.format(**{
+                k: url_quote(v) for k, v in values.items()
+                if k in self._regex._named_fields
+            })
+            formatted_domain = domain_rule.format(**{
+                k: url_quote(v) for k, v in values.items()
+                if k in domain_rule_parsed._named_fields
+            })
+        except (KeyError, ValueError):
+            return None
+
+        if append_unknown:
+            leftovers = {
+                k: v for k, v in values.items()
+                if k not in self._regex._named_fields
+                and k not in domain_rule_parsed._named_fields
+            }
+            if leftovers:
+                formatted_url += u'?' + url_encode(
+                    leftovers, charset=self.map.charset,
+                    sort=self.map.sort_parameters,
+                    key=self.map.sort_key)
+
+        return formatted_domain, formatted_url