2013-06-16 10 views
7

voglio definire una regola di URL con tre componenti variabili, come:URL conflitti di routing per i file statici in server di dev Flask

@app.route('/<var_1>/<var_2>/<var3>/') 

ma trovo che il server di sviluppo di valutare tali norme prima di provare a corrispondere per statica File. Quindi nulla di simile:

/static/images/img.jpg 

sarà catturato dalla mia regola url, invece di essere trasmesso al gestore di file statici built-in. C'è un modo per forzare prima il server di sviluppo a trovare i file statici?

P.S. Questo è solo un problema se la regola ha più di due componenti variabili.

+0

Un paio di interessante si legge: [sistema di routing Flask] (http://flask.pocoo.org/docs/design/#the-routing-system) e [Werkzeug di routing] (http: // Werkzeug .pocoo.org/docs/routing/# modulo-werkzeug.routing). Potrebbe anche voler esplorare usando [Blueprints] (http://flask.pocoo.org/docs/blueprints/) –

risposta

16

Questa è la funzione di ottimizzazione del percorso di werkzeug. Vedere Map.add, Map.update e Rule.match_compare_key:

def match_compare_key(self): 
    """The match compare key for sorting. 

    Current implementation: 

    1. rules without any arguments come first for performance 
    reasons only as we expect them to match faster and some 
    common ones usually don't have any arguments (index pages etc.) 
    2. The more complex rules come first so the second argument is the 
    negative length of the number of weights. 
    3. lastly we order by the actual weights. 

    :internal: 
    """ 
    return bool(self.arguments), -len(self._weights), self._weights 

ci sono self.arguments - argomenti attuali, self._weights - profondità di percorso.

Per '/<var_1>/<var_2>/<var3>/' abbiamo (True, -3, [(1, 100), (1, 100), (1, 100)]). Ci sono (1, 100) - argomento stringa predefinito con lunghezza massima 100.

Per '/static/<path:filename>' abbiamo (True, -2, [(0, -6), (1, 200)]). Ci sono (0, 1) - percorso non argomento lunghezza della stringa static, (1, 200) - percorso argomento stringa di lunghezza max 200.

Quindi non trovo alcun bel modo per impostare proprio Map implementazione per Flask.url_map o impostare la priorità per la regola mappa. Soluzioni:

  1. Installazione Flask Applicazione come app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static').
  2. Cambia @app.route('/<var_1>/<var_2>/<var3>/') a @app.route('/prefix/<var_1>/<var_2>/<var3>/').
  3. Aggiungere il proprio convertitore e utilizzare come @app.route('/<no_static:var_1>/<var_2>/<var3>/').
  4. Import werkzeug.routing, creare la propria implementazione della mappa, modificare werkzeug.routing.Map per l'implementazione, importare flask.
  5. Utilizzare il server come in produzione.
+0

Ok, ho frainteso la tua risposta all'inizio. Quindi con l'opzione 1, si modifica l'url a cui sono serviti i file statici e si aggiungono abbastanza sottodirectory "fittizie" a quell'url in modo che non possa eventualmente interferire con le regole url esistenti composte da componenti variabili. Quindi, per il mio esempio, il 'static_url_path' dovrebbe essere cambiato in qualcosa come:'/static/static/static/static' e quindi non verrebbe più catturato da '@ app.route ('/ ///') ' –

+0

Ho usato l'opzione 1. Non so perché, ma non funzionerebbe quando ho avuto l'argomento static_path ma ho funzionato quando l'ho rimosso. Ho finito per usare: 'app = Flask (__ name__, static_url_path = '/ r/s/static')' – trevor

6

Così, come tbicr sottolineato, questo comportamento si trova nel profondo Werkzeug, e non c'è davvero un modo elegante per gestire la cosa da Flask. La soluzione migliore che potrei venire in mente è:

Definire un gestore di file statici complementari come:

@app.route('/static/<subdir>/<path:filename>/') 
def static_subdir(subdir=None, filename=None): 

    directory = app.config['STATIC_FOLDER'] + subdir 
    return send_from_directory(directory, filename) 

Qui, app.config['STATIC_FOLDER'] è il percorso completo della cartella statica sulla macchina che esegue l'applicazione.

Ora, questo gestore rileva elementi come /static/images/img.jpg, lasciando la mia vista con i tre componenti variabili da solo.

+0

È una buona idea, ma non è necessario aggiungere il proprio handler perché è possibile farlo con le impostazioni dell'applicazione 'Flask'. Vedere la mia soluzione di risposta 1: installazione Flask come 'app = Flask (static_path = 'static', static_url_path = '/ more/then/your/max/variables/path/depth/static')' ma cambierà il percorso dell'url statico (nessun problema per 'url_for ('static', filename = 'images/img.jpg''.) – tbicr

+0

IMO la tua soluzione è la migliore menzionata qui, petrus, l'introduzione di sottodirectory fittizie è certamente più brutta di quella che la cattura esplicitamente in una regola URL. –

3

Un modo per aggirare questo è di imbrogliare l'algoritmo di ordinamento delle regole spoofing il metodo match_compare_key() della regola registrata.Nota che questo hack funziona solo con le rotte che sono state registrate direttamente con app.route() (l'oggetto Flask), non con Blueprints. Le rotte dei progetti vengono aggiunte all'url globale solo se la registrazione del modello viene eseguita sull'app principale, rendendo difficile la modifica delle regole generate.

# an ordinary route 
@app.route('/<var1>/<var2>/<var3>') 
def some_view(var1, var2, var3): 
    pass 

# let's find the rule that was just generated 
rule = app.url_map._rules[-1] 

# we create some comparison keys: 
# increase probability that the rule will be near or at the top 
top_compare_key = False, -100, [(-2, 0)] 
# increase probability that the rule will be near or at the bottom 
bottom_compare_key = True, 100, [(2, 0)] 

# rig rule.match_compare_key() to return the spoofed compare_key 
rule.match_compare_key = lambda: top_compare_key 

Si noti che in questo caso la funzione di spoofing risultante non è associata all'oggetto rule. Pertanto, alla chiamata rule.match_compare_key(), la funzione non riceve un argomento self. Se si desidera associare la funzione correttamente, fare questo, invece:

spoof = lambda self: top_compare_key 
rule.match_compare_key = spoof.__get__(rule, type(rule)) 

possiamo generalizzare quanto sopra con un decoratore

def weighted_route(*args, **kwargs): 
    def decorator(view_func): 
     compare_key = kwargs.pop('compare_key', None) 
     # register view_func with route 
     app.route(*args, **kwargs)(view_func) 

     if compare_key is not None: 
      rule = app.url_map._rules[-1] 
      rule.match_compare_key = lambda: compare_key 

     return view_func 
    return decorator 

# can be used like @app.route(). To weight the rule, just provide 
# the `compare_key` param. 
@weighted_route('/<var1>/<var2>/<var3>', compare_key=bottom_compare_key) 
def some_view(var1, var2, var3): 
    pass 

Lo stesso trucco implementato come un contesto manager.

import contextlib 

@contextlib.contextmanager 
def weighted_route(compare_key=None): 
    yield 
    if compare_key is not None: 
     rule = app.url_map._rules[-1] 
     rule.match_compare_key = lambda: compare_key 

# and to use 

with weighted_route(compare_key): 
    @app.route('/<var1>/<var2>/<var3>') 
    def some_view(var1, var2, var3): 
     pass 
Problemi correlati