Custom Bench command

Hello, the only similar topic I found is:

Yet, there was no answer to the question how to make a custom bench command.
I am running latest v7 frappe/erpnext, bench 4.1.0 and by what I found on the net, I was able to write this:

frappe-bench/apps/test/test/commands/importer.py :

from __future__ import unicode_literals, absolute_import
import click
from bench.commands import bench_command

@click.command('test_importer')
@click.argument('a', None)
def test_importer(a=None):
    print "hello"
    if a:
        print a

commands = [
    test_importer
]

bench_command.add_command(test_importer)

Still I get error:

root: .... /frappe-bench# bench test_importer "a"
Usage: bench [OPTIONS] COMMAND [ARGS]...
Error: No such command "test_importer".

I also tried with no success:

bench --site site1.local test_importer "a"
bench --site site1.local test_importer --a "a"
bench --site site1.local test_importer -a "a"
bench --site site1.local test_importer
bench test_importer

I have been able to run the code by the following bench command from another file:

bench --site site1.local execute --args "a" test.importer.run

Yet I want to do it by a custom bench command, as the params in execute do not have names for input, while the custom command can give more hints for them.

Tried putting the command in the hooks.py and I got a lot of errors on bench start. I really do not know what to do here.

Tried with no success to define the custom command in apps/test/test/commands.py
Do I have to specify somewhere, which is my command file?
Can anyone give me some pointers?

Edit1:

by going deeper into frappe code, I managed to find:

click.Group(commands=commands)(prog_name='bench')

Still, No idea how to use it or should I use it.

Edit2:

Okey, so there is a connection to the First Edit, but I do not have / should not / must not use it directly.
Instead I should hook up to main function in frappe/utils/bench_helper.py
Okey, so far so good. main → get_app_groups → get_app_group → get_app_commands(app_name) function is trying to hook into every app namespace looking for module commands and getting from it commands array! So if I define my command in _init_.py of commands module of my app, that should do it?

Seems not yet, there is more I am missing.

Edit3:

So… commands in _init_.py is not an array, it is a list with object/s ? … Okey
Trying to set my commands variable to a list( set( [test_importer] ) ). Keeping in mind that the variable is in the “test” namespace and “commands” module.

And… Nope, not yet.

Edit4:

Finally! I am doing something! Too bad that the server is breaking, but still it’s a change!
So lets see what I’m doing:

apps/test/test/commands/_init_.py :

from __future__ import unicode_literals, absolute_import
import click

@click.command('test_importer')
@click.pass_context
@click.option('--site')
def test_importer(context):
    print "hello"


commands = {"test_importer": test_importer}

and on bench start I get errors like this one:

Error: No such command "worker".
Usage: bench [OPTIONS] COMMAND [ARGS]...

So, I am overwriting all other commands :frowning:

Edit5:

Judging by the code:

for command in getattr(app_command_module, 'commands', []):
	ret[command.name] = command

commands is a list with functions? Did not know the function has .name :-/
With this I fixed the server start, but again the command does not run.

Edit6:

As revant_one bellow has pointed out, the name of the command has to be with “-” instead of “_”

# commands.py
from __future__ import unicode_literals, absolute_import
import click

@click.command('test-importer')
def test_importer():
    print("hello")

commands = [test_importer]

Too bad it is not working for me. I will continue Monday.

1 Like

I tried to copy the effect of translator app as mentioned in shared post and it worked.

Added file commands.py in apps/custom_app/custom_app/commands.py

# commands.py
from __future__ import unicode_literals, absolute_import
import click

@click.command('test-importer')
@click.argument('a', None)
def test_importer(a=None):
    print("hello")
    if a:
        print a

commands = [test_importer]

It works

revant@revant-laptop:~/frappe-bench$ bench test-importer 
Usage: bench  test-importer [OPTIONS] A

Error: Missing argument "None".
revant@revant-laptop:~/frappe-bench$ bench test-importer --help
Usage: bench  test-importer [OPTIONS] A

Options:
  --help  Show this message and exit.
1 Like

Updated Edit6, above.

Although bench new-custom-command will be clean for users of your app.

There is also the bench execute path.to.custom.method. Read json/csv file(s) from that method.

1 Like

The following code is running smoothly with me.

from __future__ import unicode_literals, absolute_import
from my.custom.app import my_method
import click
import os

@click.command('custom-command')
@click.argument('path', required=False)
def custom_command(path=None):
    if not path:
        path = "./dir/path/"

    print(path)
    my_method(path)

commands = [custom_command]

But I have some issue when I call frappe.get_all('Doctype', 'Docname')
in my_method() I get an error says

...
File "/home/yasin/new-bench/apps/frappe/frappe/__init__.py", line 1301, in get_all
    return get_list(doctype, *args, **kwargs)
  File "/home/yasin/new-bench/apps/frappe/frappe/__init__.py", line 1274, in get_list
    return frappe.model.db_query.DatabaseQuery(doctype).execute(None, *args, **kwargs)
  File "/home/yasin/new-bench/apps/frappe/frappe/model/db_query.py", line 29, in __init__
    self.user = user or frappe.session.user
  File "/home/yasin/new-bench/env/lib/python3.7/site-packages/werkzeug/local.py", line 348, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/home/yasin/new-bench/env/lib/python3.7/site-packages/werkzeug/local.py", line 311, in _get_current_object
    raise RuntimeError("no object bound to %s" % self.__name__)
RuntimeError: no object bound to session

Can anybody help me with this? Or anybody has any idea why I’m getting this error?

I think you are missing connecting to a site. You may have to pass in the site name as an option. This worked for me.

bench start-telethon --site my_site_name

@click.command("start-telethon")
@click.option("--site", help="site name")
@pass_context
def start_telethon(context, site):
    frappe.init(site)
    if not frappe.db:
        frappe.connect()

    for d in frappe.get_all("Sales Order", "name", limit=10):
        print(d)