Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: add variable or partial match support in urls #34

Closed
arturpat opened this issue Jun 16, 2020 · 4 comments · Fixed by #37
Closed

Idea: add variable or partial match support in urls #34

arturpat opened this issue Jun 16, 2020 · 4 comments · Fixed by #37

Comments

@arturpat
Copy link

While I find pytest-httpserver extremely useful and use it daily, I find that it's missing one feature - it's not possible to expect_request() with a variable in it or expect_request() with a partial match. Example use case would be testing a DELETE operation. I'd like to mock an api endpoint that handles customer deletion by url (e.g. /customer/42). I'd like to be able to expect a any request that follows the patter of /customer/<customer_id>. Thanks for the good work!

@csernazs
Copy link
Owner

There are two aspects of this issue.

you can already do it

pytest-httpserver was written in a way that you can extend it so you can:

Specify URI_DEFAULT value as the URI, in this case your response handler will be called for each and every URI (provided that other criteria matches), so you can do the URL selection there. You can specify a function (callable) as the request handler so you can do the fine tuning there.

Something like that (I haven't tried it):

from pytest_httpserver import URI_DEFAULT

def custom_handler(request):
   # additional logic goes here

httpserver.expect_request(URI_DEFAULT).respond_with_handler(custom_handler)

Important: dispatching the request to a handler happens in the same order the handlers are registered so once you register the "catch all" handler, further registered handlers won't be called (unless the "catch all" handler defines a method or some other criteria which is not matches with the actual request).

Alternatively, you can still override the mechanism of how the dispaching works, you can look at the source code to create_matcher() method what you can override so you can use a different request matcher object here.

why it is not implemented yet

This library uses werkzeug behind the scenes, which is the same library Flask uses for http traffic. Basically it calls a single function when a request arrives, and provides the Request object and expects a Response object. It is super great as I don't need to deal with http parsing. On the other hand: I need to decide which function (handler) I must call. This is implemented in the dispatch() function if you look at the source code.

The URL matching feature you are missing opens the scope of this library a lot, imagine that there are multiple matchers for the same URI, in this case the library would need to decide which handler it should call (if you ask me, I would chose the most strict URI pattern, this is what Flask is doing AFAIK). If you look at Flask (or basically other web frameworks), the very key component is the routing functionality which is exactly the same what you are missing here. If they did it pretty well (and I'm pretty sure about it), I'm not entirely sure that I need to re-invent it.

When I wrote this library initially, I thought that it could use Flask behind the scenes, but frankly, my lib wouldn't add much functionality to it, I mean that if you need a more complex thing you can still create your mock in Flask and spawn it from a fixture and that's done.

@arturpat
Copy link
Author

Thanks for your quick response! I understand your points. I will try to implement a solution basing on pytest_httpserver.URI_DEFAULT, thank you.

@csernazs csernazs reopened this Jun 17, 2020
@csernazs
Copy link
Owner

csernazs commented Jun 17, 2020

Just another idea is to define a URLPattern class, implement the __eq__ method, and profit. :)

Works for me:

class URLPattern:
    def __init__(self, prefix):
        self.prefix = prefix

    def __eq__(self, other: str):
        return other.startswith(self.prefix)

def test_urlpattern(httpserver: HTTPServer):
    httpserver.expect_request(URLPattern("/foo")).respond_with_json({"foo": "bar"})
    assert requests.get(httpserver.url_for("/foobar")).json() == {'foo': 'bar'}

The only issue is that it breaks the type hinting rules. And also, you are not able to get any value from the URL.

@arturpat
Copy link
Author

arturpat commented Jun 17, 2020

This is cool and it works great, thanks for the idea! I am able to get values from the url by using respond_with_handler() and handling the logic there. This really solves my issue, thank you.

csernazs added a commit that referenced this issue Aug 3, 2020
Extend URI matching functionality which now accepts:

* a URIPattern object, whose match() method will be called
* a compiled regexp returned by re.compile(). In this case the
  URI will be matched against the regexp.

In both cases the URI will be an absolute path starting with a slash.

Fixes #34 and #36.
csernazs added a commit that referenced this issue Aug 3, 2020
Extend URI matching functionality which now accepts:

* a URIPattern object, whose match() method will be called
* a compiled regexp returned by re.compile(). In this case the
  URI will be matched against the regexp.

In both cases the URI will be an absolute path starting with a slash.

Fixes #34 and #36.
csernazs added a commit that referenced this issue Aug 5, 2020
Extend URI matching functionality which now accepts:

* a URIPattern object, whose match() method will be called
* a compiled regexp returned by re.compile(). In this case the
  URI will be matched against the regexp.

In both cases the URI will be an absolute path starting with a slash.

Fixes #34 and #36.
csernazs added a commit that referenced this issue Aug 5, 2020
Extend URI matching functionality which now accepts:

* a URIPattern object, whose match() method will be called
* a compiled regexp returned by re.compile(). In this case the
  URI will be matched against the regexp.

In both cases the URI will be an absolute path starting with a slash.

Fixes #34 and #36.
csernazs added a commit that referenced this issue Aug 5, 2020
Extend URI matching functionality which now accepts:

* a URIPattern object, whose match() method will be called
* a compiled regexp returned by re.compile(). In this case the
  URI will be matched against the regexp.

In both cases the URI will be an absolute path starting with a slash.

Fixes #34 and #36.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants