Repository: jina-ai/serve
Pull request: fix: remove prometheus import from the top of the files
We want to be able to remove prometheus from core dependency if needed. Therefore we need to remove the import from the top of the file

Problem statement:
perf version of v3.3.2 throw error `No module named 'prometheus_client'`
**Describe the bug**

`docker run --rm --entrypoint jina jinaai/jina:3-py37-perf -v`

<details>
  <summary>Result</summary>

```bash
❯ docker run --rm --entrypoint jina jinaai/jina:3-py37-perf -v
Unable to find image 'jinaai/jina:3-py37-perf' locally
3-py37-perf: Pulling from jinaai/jina
6d4a449ac69c: Already exists
ac1330f40b39: Already exists
4d9a1c11a1ed: Already exists
fa701256a623: Already exists
cc3042fcb5c9: Already exists
996c43f651d4: Already exists
9fde4d3a93d0: Already exists
02d6bd1d947b: Already exists
5e5b8f91eeac: Already exists
Digest: sha256:463b0b75c7a38f84f4a206c016ae6f2236b6791ff951e7181caff53c82923afc
Status: Downloaded newer image for jinaai/jina:3-py37-perf
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│                                                                              │
│ /usr/local/bin/jina:8 in <module>                                            │
│                                                                              │
│   5 from cli import main                                                     │
│   6 if __name__ == '__main__':                                               │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])     │
│ ❱ 8 │   sys.exit(main())                                                     │
│   9                                                                          │
│ /usr/local/lib/python3.7/site-packages/cli/__init__.py:202 in main           │
│                                                                              │
│   199 │   │                                                                  │
│   200 │   │   from cli import api                                            │
│   201 │   │                                                                  │
│ ❱ 202 │   │   args = _get_run_args()                                         │
│   203 │   │                                                                  │
│   204 │   │   getattr(api, args.cli.replace('-', '_'))(args)                 │
│   205                                                                        │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/cli/__init__.py:10 in _get_run_args   │
│                                                                              │
│     7                                                                        │
│     8                                                                        │
│     9 def _get_run_args(print_args: bool = True):                            │
│ ❱  10 │   from jina.helper import get_rich_console                           │
│    11 │   from jina.parsers import get_main_parser                           │
│    12 │                                                                      │
│    13 │   console = get_rich_console()                                       │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/__init__.py:196 in <module>      │
│                                                                              │
│   193                                                                        │
│   194 # Client                                                               │
│   195 from jina.clients import Client                                        │
│ ❱ 196 from jina.orchestrate.flow.asyncio import AsyncFlow                    │
│   197                                                                        │
│   198 # Flow                                                                 │
│   199 from jina.orchestrate.flow.base import Flow                            │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/orchestrate/flow/asyncio.py:1 in │
│ <module>                                                                     │
│                                                                              │
│ ❱  1 from jina.orchestrate.flow.base import Flow                             │
│    2 from jina.clients.mixin import AsyncPostMixin                           │
│    3                                                                         │
│    4                                                                         │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/orchestrate/flow/base.py:60 in   │
│ <module>                                                                     │
│                                                                              │
│     57 )                                                                     │
│     58 from jina.jaml import JAMLCompatible                                  │
│     59 from jina.logging.logger import JinaLogger                            │
│ ❱   60 from jina.orchestrate.deployments import Deployment                   │
│     61 from jina.orchestrate.flow.builder import _hanging_deployments, allow │
│     62 from jina.parsers import (                                            │
│     63 │   set_client_cli_parser,                                            │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/orchestrate/deployments/__init__ │
│ .py:13 in <module>                                                           │
│                                                                              │
│    10 from jina.helper import CatchAllCleanupContextManager                  │
│    11 from jina.hubble.hubio import HubIO                                    │
│    12 from jina.jaml.helper import complete_path                             │
│ ❱  13 from jina.orchestrate.pods.container import ContainerPod               │
│    14 from jina.orchestrate.pods.factory import PodFactory                   │
│    15 from jina.serve.networking import GrpcConnectionPool, host_is_local, i │
│    16                                                                        │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/orchestrate/pods/__init__.py:15  │
│ in <module>                                                                  │
│                                                                              │
│    12 from jina.helper import typename                                       │
│    13 from jina.jaml import JAML                                             │
│    14 from jina.logging.logger import JinaLogger                             │
│ ❱  15 from jina.orchestrate.pods.helper import ConditionalEvent, _get_event, │
│    16 from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime            │
│    17                                                                        │
│    18 __all__ = ['BasePod', 'Pod']                                           │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/orchestrate/pods/helper.py:12 in │
│ <module>                                                                     │
│                                                                              │
│     9 from jina.enums import GatewayProtocolType, PodRoleType, RuntimeBacken │
│    10 from jina.hubble.helper import is_valid_huburi                         │
│    11 from jina.hubble.hubio import HubIO                                    │
│ ❱  12 from jina.serve.networking import GrpcConnectionPool                   │
│    13 from jina.types.request.control import ControlRequest                  │
│    14                                                                        │
│    15 if TYPE_CHECKING:                                                      │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/serve/networking.py:16 in        │
│ <module>                                                                     │
│                                                                              │
│    13                                                                        │
│    14 from jina.enums import PollingType                                     │
│    15 from jina.logging.logger import JinaLogger                             │
│ ❱  16 from jina.proto import jina_pb2_grpc                                   │
│    17 from jina.types.request import Request                                 │
│    18 from jina.types.request.control import ControlRequest                  │
│    19 from jina.types.request.data import DataRequest                        │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/proto/jina_pb2_grpc.py:5 in      │
│ <module>                                                                     │
│                                                                              │
│     2 """Client and server classes corresponding to protobuf-defined service │
│     3 import grpc                                                            │
│     4                                                                        │
│ ❱   5 from . import serializer as jina__pb2                                  │
│     6                                                                        │
│     7                                                                        │
│     8 class JinaControlRequestRPCStub(object):                               │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/proto/serializer.py:5 in         │
│ <module>                                                                     │
│                                                                              │
│     2 from typing import Iterable, List, Union                               │
│     3                                                                        │
│     4 from jina.proto import jina_pb2                                        │
│ ❱   5 from jina.types.request.control import ControlRequest                  │
│     6 from jina.types.request.data import DataRequest                        │
│     7                                                                        │
│     8                                                                        │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/types/request/__init__.py:4 in   │
│ <module>                                                                     │
│                                                                              │
│    1 import traceback                                                        │
│    2 from typing import Optional                                             │
│    3                                                                         │
│ ❱  4 from jina.serve.executors import BaseExecutor                           │
│    5 from jina.proto import jina_pb2                                         │
│    6 from jina.types.mixin import ProtoTypeMixin                             │
│    7                                                                         │
│                                                                              │
│ /usr/local/lib/python3.7/site-packages/jina/serve/executors/__init__.py:9 in │
│ <module>                                                                     │
│                                                                              │
│     6 from types import SimpleNamespace                                      │
│     7 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Uni │
│     8                                                                        │
│ ❱   9 from prometheus_client import Summary                                  │
│    10                                                                        │
│    11 from jina import __args_executor_init__, __default_endpoint__          │
│    12 from jina.enums import BetterEnum                                      │
╰──────────────────────────────────────────────────────────────────────────────╯
ModuleNotFoundError: No module named 'prometheus_client'
```

</details>

**Describe how you solve it**
Move `prometheus_client` from `standard` to `core` level

---

In addition: 

**We need to add a sanity check to make sure the perf version can always work. Because Hub heavily depends on it. It's the 2rd or 3rd time we meet the same error.**

Hints from issue discussion:
Ah. I noticed that #4641 is trying to fix this.

Commit message: feat: add github action to test import

Patch to review:
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 126ace6009f2d..58abe05b60ee2 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -157,6 +157,28 @@ jobs:
           flags: ${{ steps.test.outputs.codecov_flag }}
           fail_ci_if_error: false
 
+  import-test:
+    runs-on: ubuntu-latest
+    needs: [ commit-lint, lint-flake-8, code-injection ]
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [ 3.7 ]
+        test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}}
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Prepare enviroment
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install wheel
+          pip install --no-cache-dir .
+      - name: Test basic import
+        run: python -c 'from jina import Executor,requests'
+
   docker-image-test:
     needs: update-schema
     runs-on: ubuntu-latest
@@ -317,7 +339,7 @@ jobs:
   # just for blocking the merge until all parallel core-test are successful
   success-all-steps:
     runs-on: ubuntu-latest
-    needs: [core-test, hub-test, k8s-test, docker-compose-test, docker-image-test, benchmark-pre-release, update-schema, update-docker]
+    needs: [core-test, import-test, hub-test, k8s-test, docker-compose-test, docker-image-test, benchmark-pre-release, update-schema, update-docker]
     if: always()
     steps:
       - uses: technote-space/workflow-conclusion-action@v2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3beb23b71abe4..a64004cc856e9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -304,10 +304,33 @@ jobs:
           flags: ${{ steps.test.outputs.codecov_flag }}
           fail_ci_if_error: false
 
+  import-test:
+    runs-on: ubuntu-latest
+    needs: [ commit-lint, lint-flake-8, code-injection ]
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [ 3.7 ]
+        test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}}
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Prepare enviroment
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install wheel
+          pip install --no-cache-dir .
+      - name: Test basic import
+        run: python -c 'from jina import Executor,requests'
+
+
   # just for blocking the merge until all parallel core-test are successful
   success-all-test:
     runs-on: ubuntu-latest
-    needs: [core-test, hub-test, k8s-test, docker-compose-test, docker-image-test, check-docstring, check-black, code-injection]
+    needs: [core-test, import-test, hub-test, k8s-test, docker-compose-test, docker-image-test, check-docstring, check-black, code-injection]
     if: always()
     steps:
       - uses: technote-space/workflow-conclusion-action@v2
diff --git a/jina/serve/executors/__init__.py b/jina/serve/executors/__init__.py
index 9f6290ce263c8..9b83ac25b3608 100644
--- a/jina/serve/executors/__init__.py
+++ b/jina/serve/executors/__init__.py
@@ -6,11 +6,10 @@
 from types import SimpleNamespace
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
 
-from prometheus_client import Summary
-
 from jina import __args_executor_init__, __default_endpoint__
 from jina.enums import BetterEnum
 from jina.helper import ArgNamespace, T, iscoroutinefunction, typename
+from jina.importer import ImportExtensions
 from jina.jaml import JAML, JAMLCompatible, env_var_regex, internal_var_regex
 from jina.serve.executors.decorators import requests, store_init_kwargs, wrap_func
 
@@ -115,7 +114,16 @@ def _add_runtime_args(self, _runtime_args: Optional[Dict]):
             self.runtime_args = SimpleNamespace()
 
     def _init_monitoring(self):
-        if hasattr(self.runtime_args, 'metrics_registry'):
+        if (
+            hasattr(self.runtime_args, 'metrics_registry')
+            and self.runtime_args.metrics_registry
+        ):
+            with ImportExtensions(
+                required=True,
+                help_text='You need to install the `prometheus_client` to use the montitoring functionality of jina',
+            ):
+                from prometheus_client import Summary
+
             self._summary_method = Summary(
                 'process_request_seconds',
                 'Time spent when calling the executor request method',
diff --git a/jina/serve/executors/decorators.py b/jina/serve/executors/decorators.py
index b670f63a5b675..92274c8b78a86 100644
--- a/jina/serve/executors/decorators.py
+++ b/jina/serve/executors/decorators.py
@@ -14,12 +14,12 @@
     Union,
 )
 
-from prometheus_client import Summary
-
 from jina.helper import convert_tuple_to_list, iscoroutinefunction
 from jina.serve.executors.metas import get_default_metas
 
 if TYPE_CHECKING:
+    from prometheus_client import Summary
+
     from jina import DocumentArray
 
 
diff --git a/jina/serve/networking.py b/jina/serve/networking.py
index 76748029cbe52..ee60071094e1d 100644
--- a/jina/serve/networking.py
+++ b/jina/serve/networking.py
@@ -12,6 +12,7 @@
 from grpc_reflection.v1alpha.reflection_pb2_grpc import ServerReflectionStub
 
 from jina.enums import PollingType
+from jina.importer import ImportExtensions
 from jina.logging.logger import JinaLogger
 from jina.proto import jina_pb2_grpc
 from jina.types.request import Request
@@ -408,7 +409,11 @@ def __init__(
         )
 
         if metrics_registry:
-            from prometheus_client import Summary
+            with ImportExtensions(
+                required=True,
+                help_text='You need to install the `prometheus_client` to use the montitoring functionality of jina',
+            ):
+                from prometheus_client import Summary
 
             self._summary_time = Summary(
                 'sending_request_seconds',
diff --git a/jina/serve/runtimes/gateway/request_handling.py b/jina/serve/runtimes/gateway/request_handling.py
index a3023dc452a4a..39f1936289bea 100644
--- a/jina/serve/runtimes/gateway/request_handling.py
+++ b/jina/serve/runtimes/gateway/request_handling.py
@@ -5,6 +5,7 @@
 
 from docarray import DocumentArray
 
+from jina.importer import ImportExtensions
 from jina.serve.networking import GrpcConnectionPool
 from jina.serve.runtimes.gateway.graph.topology_graph import TopologyGraph
 
@@ -25,7 +26,11 @@ def __init__(self, metrics_registry: Optional['CollectorRegistry'] = None):
         self.request_init_time = {} if metrics_registry else None
 
         if metrics_registry:
-            from prometheus_client import Summary
+            with ImportExtensions(
+                required=True,
+                help_text='You need to install the `prometheus_client` to use the montitoring functionality of jina',
+            ):
+                from prometheus_client import Summary
 
             self._summary = Summary(
                 'receiving_request_seconds',
diff --git a/jina/serve/runtimes/head/__init__.py b/jina/serve/runtimes/head/__init__.py
index fc300ea202ae8..ad26c57b53a9e 100644
--- a/jina/serve/runtimes/head/__init__.py
+++ b/jina/serve/runtimes/head/__init__.py
@@ -13,6 +13,7 @@
 from grpc_reflection.v1alpha import reflection
 
 from jina.enums import PollingType
+from jina.importer import ImportExtensions
 from jina.proto import jina_pb2, jina_pb2_grpc
 from jina.serve.networking import GrpcConnectionPool
 from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime
@@ -54,7 +55,11 @@ def __init__(
         )
 
         if self.metrics_registry:
-            from prometheus_client import Summary
+            with ImportExtensions(
+                required=True,
+                help_text='You need to install the `prometheus_client` to use the montitoring functionality of jina',
+            ):
+                from prometheus_client import Summary
 
             self._summary = Summary(
                 'receiving_request_seconds',
diff --git a/jina/serve/runtimes/monitoring.py b/jina/serve/runtimes/monitoring.py
index 74eb4b3691e25..6ce229b679129 100644
--- a/jina/serve/runtimes/monitoring.py
+++ b/jina/serve/runtimes/monitoring.py
@@ -1,11 +1,6 @@
-from prometheus_client import CollectorRegistry
-
-
 class MonitoringMixin:
     """The Monitoring Mixin for pods"""
 
-    metrics_registry: CollectorRegistry
-
     def _setup_monitoring(self):
         """
         Wait for the monitoring server to start
diff --git a/jina/serve/runtimes/worker/__init__.py b/jina/serve/runtimes/worker/__init__.py
index 630729e5bffe2..4f06cd180e045 100644
--- a/jina/serve/runtimes/worker/__init__.py
+++ b/jina/serve/runtimes/worker/__init__.py
@@ -9,6 +9,7 @@
 import grpc
 from grpc_reflection.v1alpha import reflection
 
+from jina.importer import ImportExtensions
 from jina.proto import jina_pb2, jina_pb2_grpc
 from jina.serve.runtimes.asyncio import AsyncNewLoopRuntime
 from jina.serve.runtimes.request_handlers.data_request_handler import DataRequestHandler
@@ -41,7 +42,11 @@ async def async_setup(self):
         await self._async_setup_grpc_server()
 
         if self.metrics_registry:
-            from prometheus_client import Summary
+            with ImportExtensions(
+                required=True,
+                help_text='You need to install the `prometheus_client` to use the montitoring functionality of jina',
+            ):
+                from prometheus_client import Summary
 
             self._summary_time = Summary(
                 'receiving_request_seconds',


Code review comments:
diff_hunk: @@ -157,6 +157,28 @@ jobs:
           flags: ${{ steps.test.outputs.codecov_flag }}
           fail_ci_if_error: false
 
+  import-test:
+    runs-on: ubuntu-latest
+    needs: [ commit-lint, lint-flake-8, code-injection ]
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [ 3.7 ]
+        test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}}
path: .github/workflows/cd.yml
text: @user1:
`test-path` is useless and could be removed.

diff_hunk: @@ -157,6 +157,28 @@ jobs:
           flags: ${{ steps.test.outputs.codecov_flag }}
           fail_ci_if_error: false
 
+  import-test:
+    runs-on: ubuntu-latest
+    needs: [ commit-lint, lint-flake-8, code-injection ]
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [ 3.7 ]
+        test-path: ${{fromJson(needs.prep-testbed.outputs.matrix)}}
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Prepare enviroment
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install wheel
+          pip install --no-cache-dir .
+      - name: Test basic import
+        run: python -c 'from jina import Executor,requests'
+
path: .github/workflows/cd.yml
text: @user2:
```suggestion
  import-test:
    runs-on: ubuntu-latest
    needs: [ commit-lint, lint-flake-8, code-injection ]
    strategy:
      fail-fast: false
      matrix:
        core: ['', 'true']
        perf: ['', 'true']
        exclude: 
          - core: 'true'
            perf: 'true'
    steps:
      - uses: actions/checkout@user1
      - name: Set up Python 3.7
        uses: actions/setup-python@user1
        with:
          python-version: 3.7
      - name: Prepare enviroment
        run: |
          python -m pip install --upgrade pip
          python -m pip install wheel
          pip install --no-cache-dir .
        env:
          JINA_PIP_INSTALL_CORE: ${{ matrix.core }}
          JINA_PIP_INSTALL_PERF: ${{ matrix.perf }}
      - name: Test basic import
        run: python -c 'from jina import Executor,requests'
```