4

I have a list of strings

l = [
   '/api/users/*',
   '/api/account/*
]

And paths are like

/api/users/add/
/api/users/edit/1
/api/users/
/api/account/view/1
/api/account/

How can I perform filter for the paths if they exist in the list l.

condition like

'/api/users/add/' in l

should return True and for all given paths above.

Anuj TBE
  • 9,198
  • 27
  • 136
  • 285
  • 4
    Please [edit] your question and include the code you've written to solve this problem. –  Feb 12 '19 at 09:30
  • If the wildcards are always at the end of a path, you don't need wildcard matching and you can just use prefix match. If you do need wildcard matching, use the appropriate library (`fnmatch`) – alexis Feb 12 '19 at 10:35

5 Answers5

3

If I understand correctly, you want to see if the wildcard pattern would hold true. For this you can use the fnmatch module from glob. Supposing you have this:

l = [
   '/api/users/*',
   '/api/account/*'
]

paths = [
   '/api/users/add/'
   '/api/users/edit/1',
   '/api/users/',
   '/api/account/view/1',
   '/api/account/',
   '/non/existent/path'
]

You could get this:

>>> import fnmatch
>>> [any(fnmatch.fnmatch(path, pat) for pat in l) for path in paths]
[True, True, True, True, False]
Bob Zimmermann
  • 938
  • 7
  • 11
2
l = [
   '/api/users/*',
   '/api/account/'
]

paths = [
'/api/users/add/'
'/api/users/edit/1',
'/api/users/',
'/api/account/view/1',
'/api/account/'
]

for path in paths:
    if path in l:
        print("Path: {}, found in the list".format(path))

OUTPUT:

Path: /api/account/, found in the list

EDIT:

If you want a method that would return the boolean value:

l = [
   '/api/users/*',
   '/api/account/'
]

paths = [
'/api/users/add/',
'/api/users/edit/1',
'/api/users/',
'/api/account/view/1',
'/api/account/'
]

def checkPath(path):
        if path in l:
            return True
        else:
            return False

for i in range(0,len(paths)):
    print(checkPath(paths[i]))

OUTPUT:

False
False
False
False
True

EDIT 2:

If you want the * ones to match with the path, you can remove the * from the original list and then iterate like:

def checkPath(path):
        if path in l_new:
            return True
        else:
            return False

# strip the asterick
l_new = [s.strip('*') for s in l]

for i in range(0,len(paths)):
    print(checkPath(paths[i]))

OUTPUT:

False
False
True
False
True
DirtyBit
  • 16,613
  • 4
  • 34
  • 55
  • What if there are paths in `l` that don't have a `*`, i.e. require a perfect match? – tobias_k Feb 12 '19 at 10:10
  • @tobias_k The first Edit would work in that case, if you remove `*` from the `l` it would return `False False True False True` – DirtyBit Feb 12 '19 at 10:11
  • 1
    But if you just remove the `*` how can you differentiate between paths that allow for a suffix and those that don't? – tobias_k Feb 12 '19 at 10:17
  • Ah, in that case, I think the best shot would be `fnmatch` as described already! – DirtyBit Feb 12 '19 at 10:39
2

If your wildcards are always the last thing in the query string, I recommend chopping it off and using .startswith(). Otherwise, use the fnmatch module which interprets "glob"-style wildcards:

from fnmatch import fnmatch

def listglob(path, patterns):
    return any(fnmatch(path, pat) for pat in patterns)

for path in paths:
    print(path, listglob(path, l))
alexis
  • 48,685
  • 16
  • 101
  • 161
1

The fnmatch solutions posted already are certainly recommended for this problem, however, the answer below demonstrates a non-import solution:

def matchs_path(_pattern, _input):
  _a, _b = filter(None, _pattern.split('/')), filter(None, _input.split('/'))
  while True:
    _val, _val2 = next(_a, None), next(_b, None)
    if _val is None and _val2 is None:
      return True
    if _val != '*' and _val != _val2:
      return False
    if _val == "*":
      _to_see = next(_a, None)
      if _to_see is None:
        return True
      while True:
        c = next(_b, None)
        if c is None:
          return True
        if c == _to_see:
          break

patterns = ['/api/users/*', '/api/account/*', '/new/*/test/here']
data = ['/api/users/add/', '/api/users/edit/1', '/api/users/', '/api/account/view/1', '/api/account/', '/going/to/fail/here', '/new/additional/abc/test/here']
new_results = {i:{c:matchs_path(i, c) for c in data} for i in patterns}

Output:

{
 "/api/users/*": {
    "/api/users/add/": true,
    "/api/users/edit/1": true,
    "/api/users/": true,
    "/api/account/view/1": false,
    "/api/account/": false,
    "/going/to/fail/here": false,
    "/new/additional/abc/test/here": false
 },
  "/api/account/*": {
    "/api/users/add/": false,
    "/api/users/edit/1": false,
    "/api/users/": false,
    "/api/account/view/1": true,
    "/api/account/": true,
    "/going/to/fail/here": false,
    "/new/additional/abc/test/here": false
 },
 "/new/*/test/here": {
    "/api/users/add/": false,
    "/api/users/edit/1": false,
    "/api/users/": false,
    "/api/account/view/1": false,
    "/api/account/": false,
    "/going/to/fail/here": false,
    "/new/additional/abc/test/here": true
  }
}
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
0

You can use a regular expression to replace a * at the end of the path pattern with .* and than use those as regular expressions themselves to match the paths in the list.

paths = ['/api/users/add/',
         '/api/users/edit/1',
         '/api/users/',
         '/api/account/view/1',
         '/api/account/',
         '/not/a/valid/path']
l = ['/api/users/*', '/api/account/*']
patterns = [re.compile(re.sub("\*$", ".*", s)) for s in l]

>>> [path for path in paths if any(p.match(path) for p in patterns)]
['/api/users/add/',
 '/api/users/edit/1',
 '/api/users/',
 '/api/account/view/1',
 '/api/account/']
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • Or you could use `fnmatch.translate` to convert all wildcard symbols for you. But why bother, when Python also has support for the glob syntax? – alexis Feb 12 '19 at 12:10