skip to Main Content

In my flask app, the shell appears to be using different code than the routes. How is this possible? I was having DNS resolution issues with flask-mail and docker, so I ran the same code in both the shell and a route. The shell ran without issue, but the route returned an error that appears to be related to this github issue. When monkey patching is on the shell errors and works fine when it doesn’t. The route errors no matter what.

I am having other issues with differing behavior in the shell and routes regarding container name references, but it’s weird to see differing behavior between the shell and routes in another aspect of the app.

flask shell

>>> import dns.resolver
>>> answers = dns.resolver.resolve('google.com', 'A')
>>> answers.canonical_name

route

@production.route('/test_dns', methods=['GET'])
@login_required
def test_dns():
    import dns.resolver
    answers = dns.resolver.resolve('google.com', 'A')
    print(f'{answers.canonical_name}')

    return ''

route error

web_1            | [2024-08-01 16:28:16 +0000] [10] [ERROR] Error handling request /production/test_dns
web_1            | Traceback (most recent call last):
web_1            |   File "/usr/local/lib/python3.12/site-packages/gunicorn/workers/base_async.py", line 55, in handle
web_1            |     self.handle_request(listener_name, req, client, addr)
web_1            |   File "/usr/local/lib/python3.12/site-packages/gunicorn/workers/base_async.py", line 108, in handle_request
web_1            |     respiter = self.wsgi(environ, resp.start_response)
web_1            |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 1478, in __call__
web_1            |     return self.wsgi_app(environ, start_response)
web_1            |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 1458, in wsgi_app
web_1            |     response = self.handle_exception(e)
web_1            |                ^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 1455, in wsgi_app
web_1            |     response = self.full_dispatch_request()
web_1            |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 869, in full_dispatch_request
web_1            |     rv = self.handle_user_exception(e)
web_1            |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 867, in full_dispatch_request
web_1            |     rv = self.dispatch_request()
web_1            |          ^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 852, in dispatch_request
web_1            |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
web_1            |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/flask_login/utils.py", line 290, in decorated_view
web_1            |     return current_app.ensure_sync(func)(*args, **kwargs)
web_1            |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/app/web/production/views.py", line 67, in test_dns
web_1            |     answers = dns.resolver.resolve('google.com', 'A')
web_1            |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1565, in resolve
web_1            |     return get_default_resolver().resolve(
web_1            |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1321, in resolve
web_1            |     timeout = self._compute_timeout(start, lifetime, resolution.errors)
web_1            |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1            |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1075, in _compute_timeout
web_1            |     raise LifetimeTimeout(timeout=duration, errors=errors)
web_1            | dns.resolver.LifetimeTimeout: The resolution lifetime expired after 5.106 seconds: Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'

Minimum Reproducible Example

.
├── docker/
│   └── Dockerfile
├── app.py
├── docker-compose.yml
└── requirements.txt

docker-compose.yml

services:
  web:
    build:
      context: .
      dockerfile: ./docker/Dockerfile
    command: gunicorn --worker-class eventlet --timeout 120 -w 6 -b 0.0.0.0:5000 app:app
    # command: flask run --host=0.0.0.0 --port=5000
    volumes:
      - .:/app
    ports:
      - '5000:5000'
    environment:
      - FLASK_APP=app

requirements.txt

eventlet==0.34.2
Flask==3.0.0
gunicorn==21.2.0

docker/Dockerfile/

FROM python:3.12.4-bookworm

# making python prints go to stdout
ENV PYTHONUNBUFFERED 1

# don't write .pyc files
ENV PYTHONDONTWRITEBYTECODE 1

RUN apt-get update 
    && apt-get install -y build-essential 
    && apt-get install -y gunicorn python3-gunicorn 
    && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false 
    && rm -rf /var/lib/apt/lists/*

COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

WORKDIR /app

app.py

from flask import Flask

app = Flask(__name__, static_folder='app', static_url_path="/app")


@app.route("/test_dns")
def test_dns():
    import dns.resolver
    answers = dns.resolver.resolve('google.com', 'A')
    print(f'{answers.canonical_name}')

    return ''

In this minimal example the route only errors when command: gunicorn –worker-class eventlet –timeout 120 -w 6 -b 0.0.0.0:5000 app:app is used instead of command: flask run --host=0.0.0.0 --port=5000.

Error:

web_1  | [2024-08-02 18:00:34 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
web_1  | [2024-08-02 18:00:34 +0000] [1] [INFO] Using worker: eventlet
web_1  | [2024-08-02 18:00:34 +0000] [7] [INFO] Booting worker with pid: 7
web_1  | [2024-08-02 18:00:34 +0000] [8] [INFO] Booting worker with pid: 8
web_1  | [2024-08-02 18:00:34 +0000] [9] [INFO] Booting worker with pid: 9
web_1  | [2024-08-02 18:00:34 +0000] [10] [INFO] Booting worker with pid: 10
web_1  | [2024-08-02 18:00:34 +0000] [11] [INFO] Booting worker with pid: 11
web_1  | [2024-08-02 18:00:34 +0000] [12] [INFO] Booting worker with pid: 12
web_1  | [2024-08-02 18:01:11,183] ERROR in app: Exception on /test_dns [GET]
web_1  | Traceback (most recent call last):
web_1  |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 1455, in wsgi_app
web_1  |     response = self.full_dispatch_request()
web_1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 869, in full_dispatch_request
web_1  |     rv = self.handle_user_exception(e)
web_1  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 867, in full_dispatch_request
web_1  |     rv = self.dispatch_request()
web_1  |          ^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 852, in dispatch_request
web_1  |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
web_1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/app/app.py", line 9, in test_dns
web_1  |     answers = dns.resolver.resolve('google.com', 'A')
web_1  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1565, in resolve
web_1  |     return get_default_resolver().resolve(
web_1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1321, in resolve
web_1  |     timeout = self._compute_timeout(start, lifetime, resolution.errors)
web_1  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web_1  |   File "/usr/local/lib/python3.12/site-packages/dns/resolver.py", line 1075, in _compute_timeout
web_1  |     raise LifetimeTimeout(timeout=duration, errors=errors)
web_1  | dns.resolver.LifetimeTimeout: The resolution lifetime expired after 5.106 seconds: Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'; Server Do53:127.0.0.11@53 answered udp() got an unexpected keyword argument 'ignore_errors'

pip freeze inside container

blinker==1.8.2
click==8.1.7
dnspython==2.6.1
eventlet==0.34.2
Flask==3.0.0
greenlet==3.0.3
gunicorn==21.2.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
packaging==24.1
setuptools==71.1.0
six==1.16.0
Werkzeug==3.0.3
wheel==0.43.0

2

Answers


  1. Chosen as BEST ANSWER

    Solved by updating python modules in requirements.txt

    eventlet==0.36.1
    Flask==3.0.3
    gunicorn==22.0.0
    

  2. Glad you’ve solved your problem. For the sake of posterity, it looks like you were hitting this issue, which was opened back in Feburary 2024.

    This is a result of this change in dnspython.

    The fix was provided in this pull request, and is available in eventlet release 0.35.2.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search