Skip to content

Instantly share code, notes, and snippets.

@hexylena
Last active June 16, 2023 14:17
Show Gist options
  • Save hexylena/fe3b55419416a61c33678dd7087e3a66 to your computer and use it in GitHub Desktop.
Save hexylena/fe3b55419416a61c33678dd7087e3a66 to your computer and use it in GitHub Desktop.
Autodiscover available functions based on docstrings and type annotations

If you mark up an example function like this (only the input type annotations are really necessary), and give it a good human readable name, and then some nice pydocs.

The parameters must have descriptions as shown below:

def server_status(self, full:str="yes", tennant_id: str="") -> str:
    """
    Obtain status information about the current server process

    :param full: Show the extended results
    :param tennant_id: The tennant, this will be set automatically
    """
    r = requests.get("https://ipinfo.io/json").json()
    org = r["org"]
    ip = r["ip"]

    data = {
        "Org": org,
        "IP": ip,
        "URL": COMMIT_URL,
        "Execution Time": datetime.timedelta(seconds=time.process_time()),
        "Uptime": datetime.timedelta(seconds=time.time() - START_TIME),
        "CPU Percentage (of 100)": psutil.cpu_percent(),
        "RAM Percentage (of 100)": psutil.virtual_memory().percent,
    }

    return "\n".join([f"{k}: {v}" for (k, v) in data.items()])

Then this will get parsed properly into something like:

[{'description': 'Obtain status information about the current server process',
  'name': 'server_status',
  'parameters': {'properties': {'full': {'descrption': ' Extended results',
                                         'type': 'string'}},
                 'type': 'object'},
  'required': []}]

license

gpl3 i guess. I cannot imagine anyone actually uses this but if you want another license ask me.

def discover(self):
import inspect
TYPES = {
str: "string",
int: "integer",
}
functions = []
discovered = [x for x in inspect.getmembers(self) if not x[0].startswith('_') and x[0] != 'discover']
for (fn_name, fn) in discovered:
if not callable(fn):
continue
if fn.__doc__ is None:
continue
parsed_docstring = {
x.strip().split(':')[1].split(' ')[1]: x.split(':')[2]
for x in
fn.__doc__.split('\n')
if x.strip().startswith(':param')
}
if not parsed_docstring:
continue
sig = inspect.signature(fn)
required = []
props = {}
for p, pt in sig.parameters.items():
is_required = False
if pt.default is inspect._empty:
is_required = True
required.append(p)
print(fn_name, p, pt.annotation in TYPES, is_required)
if pt.annotation not in TYPES and not is_required:
# Skip unknown optional parameters
pass
else:
props[p] = {
"type": TYPES[pt.annotation], # Let it fail.
"descrption": parsed_docstring[p]
}
functions.append({
"name": fn_name,
"description": fn.__doc__.strip().split('\n\n')[0],
"parameters": {
"type": "object",
"properties": props
},
"required": required
})
return functions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment