Clean & Efficient Code

Jina is designed as a lean and efficient framework. Solutions built on top of Jina also mean to be so. Here are some tips to help you write beautiful and efficient code.

Clean import

  1. from jina import Document, DocumentArray, Executor, Flow, requests

is often all you need. Copy-paste it as the first line of your code.

Generator as Flow input

Use a Python generator as the input to the Flow. A generator can lazily build Documents one at a time, instead of building them all at once. This can greatly speed up overall performance and reduce the memory footprint.

✅ Do

  1. from jina import Flow, Document
  2. def my_input():
  3. for _ in range(1000):
  4. yield Document()
  5. f = Flow()
  6. with f:
  7. f.post('/', my_input)

😔 Don’t

  1. from jina import Flow, Document, DocumentArray
  2. my_input = DocumentArray([Document() for _ in range(1000)])
  3. f = Flow()
  4. with f:
  5. f.post('/', my_input)

Set request_size

request_size defines how many Documents to send in each request. When combined with a Generator, request_size determines how long it will take before sending the first request. You can change request_size to overlap the time of request generation and Flow computation.

✅ Do

  1. from jina import Flow, Document
  2. def my_input():
  3. for _ in range(1000):
  4. # big document
  5. yield Document()
  6. f = Flow().add(uses=...) # heavy computation
  7. with f:
  8. f.post('/', my_input, request_size=10)

😔 Don’t

  1. from jina import Flow, Document
  2. def my_input():
  3. for _ in range(1000):
  4. # big document
  5. yield Document()
  6. f = Flow().add(uses=...) # heavy computation
  7. with f:
  8. f.post('/', my_input, request_size=10000)

Skip unnecessary __init__

No need to implement __init__ if your Executor does not contain initial states.

✅ Do

  1. from jina import Executor
  2. class MyExecutor(Executor):
  3. def foo(self, **kwargs):
  4. ...

😔 Don’t

  1. from jina import Executor
  2. class MyExecutor(Executor):
  3. def __init__(**kwargs):
  4. super().__init__(**kwargs)
  5. def foo(self, **kwargs):
  6. ...

Skip unnecessary @requests(on=...)

Use @requests without specifying on= if your function is meant to work on all requests. You can use it for catching all requests that are not for this Executor.

✅ Do

  1. from jina import Executor, requests
  2. class MyExecutor(Executor):
  3. @requests
  4. def _skip_all(self, **kwargs):
  5. print('default do sth')

😔 Don’t

  1. from jina import Executor
  2. class MyExecutor(Executor):
  3. @requests(on='/index')
  4. def _skip_index(self, **kwargs):
  5. pass
  6. @requests(on='/search')
  7. def _skip_search(self, **kwargs):
  8. pass

Skip unnecessary **kwargs

Fold unnecessary arguments into **kwargs, only get what you need.

✅ Do

  1. from jina import Executor, requests
  2. class MyExecutor(Executor):
  3. @requests
  4. def foo_need_pars_only(self, parameters, **kwargs):
  5. print(parameters)

😔 Don’t

  1. from jina import Executor, requests
  2. class MyExecutor(Executor):
  3. @requests
  4. def foo_need_pars_only(self, docs, parameters, docs_matrix, groundtruths_matrix, **kwargs):
  5. print(parameters)

Debug Executor outside of a Flow

To debug an Executor, there is no need to use it in the Flow. Simply initiate it as an object and call its method.

✅ Do

  1. from jina import Executor, requests, DocumentArray, Document
  2. class MyExec(Executor):
  3. @requests
  4. def foo(self, docs, **kwargs):
  5. for d in docs:
  6. d.text = 'hello world'
  7. m = MyExec()
  8. da = DocumentArray([Document(text='test')])
  9. m.foo(da)
  10. print(da)

😔 Don’t

  1. from jina import Executor, requests, DocumentArray, Document, Flow
  2. class MyExec(Executor):
  3. @requests
  4. def foo(self, docs, **kwargs):
  5. for d in docs:
  6. d.text = 'hello world'
  7. da = DocumentArray([Document(text='test')])
  8. with Flow().add(uses=MyExec) as f:
  9. f.post('/', da, on_done=print)

Send parameters-only request

Send a parameters-only request to a Flow if you don’t need docs.

✅ Do

  1. from jina import Executor, Flow, requests
  2. class MyExecutor(Executor):
  3. @requests
  4. def foo_need_pars_only(self, parameters, **kwargs):
  5. print(parameters)
  6. f = Flow().add(uses=MyExecutor)
  7. with f:
  8. f.post('/foo', parameters={'hello': 'world'})

😔 Don’t

  1. from jina import Executor, Flow, Document, requests
  2. class MyExecutor(Executor):
  3. @requests
  4. def foo_need_pars_only(self, parameters, **kwargs):
  5. print(parameters)
  6. f = Flow().add(uses=MyExecutor)
  7. with f:
  8. f.post('/foo', inputs=Document(), parameters={'hello': 'world'})

Heavy lifting in the Flow, not in the Client

Heavy-lifting jobs should be put into an Executor if possible. For instance, sending high-resolution images to the Flow can be time-consuming. Putting it into an Executor can leverage the Flow to scale it. It also reduces the network overhead.

✅ Do

  1. import glob
  2. from jina import Executor, Flow, requests, Document
  3. class MyExecutor(Executor):
  4. @requests
  5. def to_blob_conversion(self, docs: DocumentArray, **kwargs):
  6. for doc in docs:
  7. doc.load_uri_to_image_blob() # conversion happens inside Flow
  8. f = Flow().add(uses=MyExecutor, replicas=2)
  9. def my_input():
  10. image_uris = glob.glob('/.workspace/*.png')
  11. for image_uri in image_uris:
  12. yield Document(uri=image_uri)
  13. with f:
  14. f.post('/foo', inputs=my_input)

😔 Don’t

  1. import glob
  2. from jina import Executor, Document
  3. def my_input():
  4. image_uris = glob.glob('/.workspace/*.png') # load high resolution images.
  5. for image_uri in image_uris:
  6. doc = Document(uri=image_uri)
  7. doc.load_uri_to_image_blob() # time consuming-job on client side
  8. yield doc
  9. f = Flow().add()
  10. with f:
  11. f.post('/foo', inputs=my_input)

Keep only necessary fields

Sometimes you do not want to pass the full Document to subsequent Executors for reasons of efficiency. You can simply use the .pop method to remove those fields.

When using Jina with an HTTP frontend, the frontend often does not need ndarray or binary content. Hence, fields such as blob, embedding, and buffer can often be removed at the last Executor before returning the final results to the frontend.

✅ Do

  1. from jina import Executor, requests
  2. class FirstExecutor(Executor):
  3. @requests
  4. def foo(self, docs, **kwargs):
  5. # some process on docs
  6. for d in docs:
  7. d.pop('embedding', 'blob')
  8. class SecondExecutor(Executor):
  9. @requests
  10. def bar(self, docs, **kwargs):
  11. # do follow up processing, but now `.embedding` and `.blob` is empty
  12. # but that's fine because this Executor does not need those fields

😔 Don’t

  1. from jina import Executor, requests
  2. class FirstExecutor(Executor):
  3. @requests
  4. def foo(self, docs, **kwargs):
  5. # some process on docs
  6. class SecondExecutor(Executor):
  7. @requests
  8. def bar(self, docs, **kwargs):
  9. # do follow up processing, even though `.embedding` and `.blob` are never used