toolslm 0.3.24__tar.gz → 0.3.26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {toolslm-0.3.24/toolslm.egg-info → toolslm-0.3.26}/PKG-INFO +4 -2
  2. {toolslm-0.3.24 → toolslm-0.3.26}/settings.ini +4 -3
  3. toolslm-0.3.26/toolslm/__init__.py +1 -0
  4. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/_modidx.py +24 -2
  5. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/download.py +11 -11
  6. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/funccall.py +22 -22
  7. toolslm-0.3.26/toolslm/inspecttools.py +252 -0
  8. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/shell.py +6 -6
  9. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/xml.py +49 -59
  10. {toolslm-0.3.24 → toolslm-0.3.26/toolslm.egg-info}/PKG-INFO +4 -2
  11. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm.egg-info/SOURCES.txt +1 -0
  12. toolslm-0.3.26/toolslm.egg-info/requires.txt +7 -0
  13. toolslm-0.3.24/toolslm/__init__.py +0 -1
  14. toolslm-0.3.24/toolslm.egg-info/requires.txt +0 -5
  15. {toolslm-0.3.24 → toolslm-0.3.26}/LICENSE +0 -0
  16. {toolslm-0.3.24 → toolslm-0.3.26}/MANIFEST.in +0 -0
  17. {toolslm-0.3.24 → toolslm-0.3.26}/README.md +0 -0
  18. {toolslm-0.3.24 → toolslm-0.3.26}/pyproject.toml +0 -0
  19. {toolslm-0.3.24 → toolslm-0.3.26}/setup.cfg +0 -0
  20. {toolslm-0.3.24 → toolslm-0.3.26}/setup.py +0 -0
  21. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm/md_hier.py +0 -0
  22. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm.egg-info/dependency_links.txt +0 -0
  23. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm.egg-info/entry_points.txt +0 -0
  24. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm.egg-info/not-zip-safe +0 -0
  25. {toolslm-0.3.24 → toolslm-0.3.26}/toolslm.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Tools to make language models a bit easier to use
5
5
  Home-page: https://github.com/AnswerDotAI/toolslm
6
6
  Author: Jeremy Howard
@@ -16,10 +16,12 @@ Classifier: License :: OSI Approved :: Apache Software License
16
16
  Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
- Requires-Dist: fastcore>=1.9.7
19
+ Requires-Dist: fastcore>=1.12.4
20
20
  Requires-Dist: httpx
21
21
  Requires-Dist: ghapi
22
+ Requires-Dist: codesigs
22
23
  Provides-Extra: dev
24
+ Requires-Dist: ipython; extra == "dev"
23
25
  Dynamic: author
24
26
  Dynamic: author-email
25
27
  Dynamic: classifier
@@ -1,11 +1,12 @@
1
1
  [DEFAULT]
2
2
  repo = toolslm
3
3
  lib_name = toolslm
4
- version = 0.3.24
4
+ version = 0.3.26
5
5
  min_python = 3.9
6
6
  license = apache2
7
7
  black_formatting = False
8
- requirements = fastcore>=1.9.7 httpx ghapi
8
+ requirements = fastcore>=1.12.4 httpx ghapi codesigs
9
+ dev_requirements = ipython
9
10
  doc_path = _docs
10
11
  lib_path = toolslm
11
12
  nbs_path = .
@@ -31,7 +32,6 @@ readme_nb = index.ipynb
31
32
  allowed_metadata_keys =
32
33
  allowed_cell_metadata_keys =
33
34
  jupyter_hooks = True
34
- clean_ids = True
35
35
  clear_all = False
36
36
  conda_user = fastai
37
37
  console_scripts = folder2ctx=toolslm.xml:folder2ctx_cli
@@ -39,4 +39,5 @@ console_scripts = folder2ctx=toolslm.xml:folder2ctx_cli
39
39
  cell_number = False
40
40
  skip_procs =
41
41
  update_pyproject = True
42
+ clean_ids = True
42
43
 
@@ -0,0 +1 @@
1
+ __version__ = "0.3.26"
@@ -33,6 +33,30 @@ d = { 'settings': { 'branch': 'main',
33
33
  'toolslm.funccall.mk_tool': ('funccall.html#mk_tool', 'toolslm/funccall.py'),
34
34
  'toolslm.funccall.python': ('funccall.html#python', 'toolslm/funccall.py'),
35
35
  'toolslm.funccall.schema2sig': ('funccall.html#schema2sig', 'toolslm/funccall.py')},
36
+ 'toolslm.inspecttools': { 'toolslm.inspecttools.SymbolNotFound': ( 'inspecttools.html#symbolnotfound',
37
+ 'toolslm/inspecttools.py'),
38
+ 'toolslm.inspecttools.SymbolNotFound.__repr__': ( 'inspecttools.html#symbolnotfound.__repr__',
39
+ 'toolslm/inspecttools.py'),
40
+ 'toolslm.inspecttools._find_frame_dict': ( 'inspecttools.html#_find_frame_dict',
41
+ 'toolslm/inspecttools.py'),
42
+ 'toolslm.inspecttools._src_from_lines': ( 'inspecttools.html#_src_from_lines',
43
+ 'toolslm/inspecttools.py'),
44
+ 'toolslm.inspecttools.importmodule': ('inspecttools.html#importmodule', 'toolslm/inspecttools.py'),
45
+ 'toolslm.inspecttools.resolve': ('inspecttools.html#resolve', 'toolslm/inspecttools.py'),
46
+ 'toolslm.inspecttools.symdir': ('inspecttools.html#symdir', 'toolslm/inspecttools.py'),
47
+ 'toolslm.inspecttools.symfiles_folder': ( 'inspecttools.html#symfiles_folder',
48
+ 'toolslm/inspecttools.py'),
49
+ 'toolslm.inspecttools.symfiles_package': ( 'inspecttools.html#symfiles_package',
50
+ 'toolslm/inspecttools.py'),
51
+ 'toolslm.inspecttools.symlen': ('inspecttools.html#symlen', 'toolslm/inspecttools.py'),
52
+ 'toolslm.inspecttools.symnth': ('inspecttools.html#symnth', 'toolslm/inspecttools.py'),
53
+ 'toolslm.inspecttools.symsearch': ('inspecttools.html#symsearch', 'toolslm/inspecttools.py'),
54
+ 'toolslm.inspecttools.symset': ('inspecttools.html#symset', 'toolslm/inspecttools.py'),
55
+ 'toolslm.inspecttools.symslice': ('inspecttools.html#symslice', 'toolslm/inspecttools.py'),
56
+ 'toolslm.inspecttools.symsrc': ('inspecttools.html#symsrc', 'toolslm/inspecttools.py'),
57
+ 'toolslm.inspecttools.symtype': ('inspecttools.html#symtype', 'toolslm/inspecttools.py'),
58
+ 'toolslm.inspecttools.symtype_val': ('inspecttools.html#symtype_val', 'toolslm/inspecttools.py'),
59
+ 'toolslm.inspecttools.symval': ('inspecttools.html#symval', 'toolslm/inspecttools.py')},
36
60
  'toolslm.md_hier': {},
37
61
  'toolslm.shell': { 'toolslm.shell.TerminalInteractiveShell.run_cell': ( 'shell.html#terminalinteractiveshell.run_cell',
38
62
  'toolslm/shell.py'),
@@ -45,14 +69,12 @@ d = { 'settings': { 'branch': 'main',
45
69
  'toolslm.xml.files2ctx': ('xml.html#files2ctx', 'toolslm/xml.py'),
46
70
  'toolslm.xml.folder2ctx': ('xml.html#folder2ctx', 'toolslm/xml.py'),
47
71
  'toolslm.xml.folder2ctx_cli': ('xml.html#folder2ctx_cli', 'toolslm/xml.py'),
48
- 'toolslm.xml.get_docstring': ('xml.html#get_docstring', 'toolslm/xml.py'),
49
72
  'toolslm.xml.get_mime_text': ('xml.html#get_mime_text', 'toolslm/xml.py'),
50
73
  'toolslm.xml.json_to_xml': ('xml.html#json_to_xml', 'toolslm/xml.py'),
51
74
  'toolslm.xml.mk_doc': ('xml.html#mk_doc', 'toolslm/xml.py'),
52
75
  'toolslm.xml.mk_doctype': ('xml.html#mk_doctype', 'toolslm/xml.py'),
53
76
  'toolslm.xml.nb2xml': ('xml.html#nb2xml', 'toolslm/xml.py'),
54
77
  'toolslm.xml.parse_gh_url': ('xml.html#parse_gh_url', 'toolslm/xml.py'),
55
- 'toolslm.xml.py2sigs': ('xml.html#py2sigs', 'toolslm/xml.py'),
56
78
  'toolslm.xml.read_file': ('xml.html#read_file', 'toolslm/xml.py'),
57
79
  'toolslm.xml.repo2ctx': ('xml.html#repo2ctx', 'toolslm/xml.py'),
58
80
  'toolslm.xml.repo2ctx_cli': ('xml.html#repo2ctx_cli', 'toolslm/xml.py'),
@@ -1,29 +1,29 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../03_download.ipynb.
2
2
 
3
- # %% auto 0
3
+ # %% auto #0
4
4
  __all__ = ['clean_md', 'read_md', 'html2md', 'read_html', 'get_llmstxt', 'split_url', 'find_docs', 'read_docs']
5
5
 
6
- # %% ../03_download.ipynb
6
+ # %% ../03_download.ipynb #e58d8c43
7
7
  from fastcore.utils import *
8
8
  from httpx import get
9
9
  from fastcore.meta import delegates
10
10
  from urllib.parse import urlparse, urljoin
11
11
  from .xml import parse_gh_url
12
12
 
13
- # %% ../03_download.ipynb
13
+ # %% ../03_download.ipynb #95c4cab1
14
14
  def clean_md(text, rm_comments=True, rm_details=True):
15
15
  "Remove comments and `<details>` sections from `text`"
16
16
  if rm_comments: text = re.sub(r'\n?<!--.*?-->\n?', '', text, flags=re.DOTALL)
17
17
  if rm_details: text = re.sub(r'\n?<details>.*?</details>\n?', '', text, flags=re.DOTALL)
18
18
  return text
19
19
 
20
- # %% ../03_download.ipynb
20
+ # %% ../03_download.ipynb #0f3d5c69
21
21
  @delegates(get)
22
22
  def read_md(url, rm_comments=True, rm_details=True, **kwargs):
23
23
  "Read text from `url` and clean with `clean_docs`"
24
24
  return clean_md(get(url, **kwargs).text, rm_comments=rm_comments, rm_details=rm_details)
25
25
 
26
- # %% ../03_download.ipynb
26
+ # %% ../03_download.ipynb #d8d61937
27
27
  def html2md(s:str, ignore_links=True):
28
28
  "Convert `s` from HTML to markdown"
29
29
  import html2text
@@ -33,7 +33,7 @@ def html2md(s:str, ignore_links=True):
33
33
  o.ignore_images = True
34
34
  return o.handle(s)
35
35
 
36
- # %% ../03_download.ipynb
36
+ # %% ../03_download.ipynb #5e897053
37
37
  def read_html(url, # URL to read
38
38
  sel=None, # Read only outerHTML of CSS selector `sel`
39
39
  rm_comments=True, # Removes HTML comments
@@ -55,7 +55,7 @@ def read_html(url, # URL to read
55
55
  if wrap_tag: return '\n'.join([f"\n<{wrap_tag}>\n{o}</{wrap_tag}>\n" for o in mds])
56
56
  else: return'\n'.join(mds)
57
57
 
58
- # %% ../03_download.ipynb
58
+ # %% ../03_download.ipynb #066b5532
59
59
  def get_llmstxt(url, optional=False, n_workers=None):
60
60
  "Get llms.txt file from and expand it with `llms_txt.create_ctx()`"
61
61
  if not url.endswith('llms.txt'): return None
@@ -64,7 +64,7 @@ def get_llmstxt(url, optional=False, n_workers=None):
64
64
  if resp.status_code!=200: return None
65
65
  return llms_txt.create_ctx(resp.text, optional=optional, n_workers=n_workers)
66
66
 
67
- # %% ../03_download.ipynb
67
+ # %% ../03_download.ipynb #a2fc5a55
68
68
  def split_url(url):
69
69
  "Split `url` into base, path, and file name, normalising name to '/' if empty"
70
70
  parsed = urlparse(url.strip('/'))
@@ -74,13 +74,13 @@ def split_url(url):
74
74
  if not path and not fname: path='/'
75
75
  return base,path,fname
76
76
 
77
- # %% ../03_download.ipynb
77
+ # %% ../03_download.ipynb #5337c0a2
78
78
  def _tryget(url):
79
79
  "Return response from `url` if `status_code!=404`, otherwise `None`"
80
80
  res = get(url)
81
81
  return None if res.status_code==404 else url
82
82
 
83
- # %% ../03_download.ipynb
83
+ # %% ../03_download.ipynb #189f5b24
84
84
  def find_docs(url):
85
85
  "If available, return LLM-friendly llms.txt context or markdown file location from `url`"
86
86
  base,path,fname = split_url(url)
@@ -100,7 +100,7 @@ def find_docs(url):
100
100
  if parsed_url.path == '/' or not parsed_url.path: return None
101
101
  return find_docs(urljoin(url, '..'))
102
102
 
103
- # %% ../03_download.ipynb
103
+ # %% ../03_download.ipynb #771d1208
104
104
  def read_docs(url, optional=False, n_workers=None, rm_comments=True, rm_details=True):
105
105
  "If available, return LLM-friendly llms.txt context or markdown file response for `url`"
106
106
  url = find_docs(url)
@@ -1,10 +1,10 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_funccall.ipynb.
2
2
 
3
- # %% auto 0
3
+ # %% auto #0
4
4
  __all__ = ['empty', 'custom_types', 'get_schema', 'python', 'mk_ns', 'call_func', 'call_func_async', 'mk_param', 'schema2sig',
5
5
  'mk_tool']
6
6
 
7
- # %% ../01_funccall.ipynb
7
+ # %% ../01_funccall.ipynb #e5ad6b86
8
8
  import inspect, json, ast
9
9
  from collections import abc
10
10
  from fastcore.utils import *
@@ -16,10 +16,10 @@ from inspect import Parameter, Signature
16
16
  from decimal import Decimal
17
17
  from uuid import UUID
18
18
 
19
- # %% ../01_funccall.ipynb
19
+ # %% ../01_funccall.ipynb #a9f43047
20
20
  empty = inspect.Parameter.empty
21
21
 
22
- # %% ../01_funccall.ipynb
22
+ # %% ../01_funccall.ipynb #e7bf4025
23
23
  def _types(t:type)->tuple[str,Optional[str]]:
24
24
  "Tuple of json schema type name and (if appropriate) array item name."
25
25
  if t is empty: raise TypeError('Missing type')
@@ -36,7 +36,7 @@ def _types(t:type)->tuple[str,Optional[str]]:
36
36
  # if t is the type itself like int, use the __name__ representation as the key
37
37
  else: return tmap.get(t.__name__, "object"), None
38
38
 
39
- # %% ../01_funccall.ipynb
39
+ # %% ../01_funccall.ipynb #4d5dc245
40
40
  def _param(
41
41
  name, # param name
42
42
  info, # dict from docments
@@ -53,7 +53,7 @@ def _param(
53
53
  else: pschema["default"] = info.default
54
54
  return pschema
55
55
 
56
- # %% ../01_funccall.ipynb
56
+ # %% ../01_funccall.ipynb #f77bea1c
57
57
  custom_types = {Path, bytes, Decimal, UUID}
58
58
 
59
59
  def _handle_type(t, defs):
@@ -66,7 +66,7 @@ def _handle_type(t, defs):
66
66
  return {'$ref': f'#/$defs/{t.__name__}'}
67
67
  return {'type': _types(t)[0]}
68
68
 
69
- # %% ../01_funccall.ipynb
69
+ # %% ../01_funccall.ipynb #7fd6cd29
70
70
  def _is_container(t):
71
71
  "Check if type is a container (list, dict, tuple, set, Union)"
72
72
  origin = get_origin(t)
@@ -76,7 +76,7 @@ def _is_parameterized(t):
76
76
  "Check if type has arguments (e.g. list[int] vs list, dict[str, int] vs dict)"
77
77
  return _is_container(t) and (get_args(t) != ())
78
78
 
79
- # %% ../01_funccall.ipynb
79
+ # %% ../01_funccall.ipynb #c1153f02
80
80
  def _handle_container(origin, args, defs):
81
81
  "Handle container types like dict, list, tuple, set, and Union"
82
82
  if origin is Union or origin is UnionType:
@@ -97,7 +97,7 @@ def _handle_container(origin, args, defs):
97
97
  return schema
98
98
  return None
99
99
 
100
- # %% ../01_funccall.ipynb
100
+ # %% ../01_funccall.ipynb #e0840bf5
101
101
  def _process_property(name, obj, props, req, defs, evalable=False):
102
102
  "Process a single property of the schema"
103
103
  p = _param(name, obj, evalable=evalable)
@@ -110,7 +110,7 @@ def _process_property(name, obj, props, req, defs, evalable=False):
110
110
  # Non-container type or container without arguments
111
111
  p.update(_handle_type(obj.anno, defs))
112
112
 
113
- # %% ../01_funccall.ipynb
113
+ # %% ../01_funccall.ipynb #38b0f97e
114
114
  def _get_nested_schema(obj, evalable=False, skip_hidden=False):
115
115
  "Generate nested JSON schema for a class or function"
116
116
  d = docments(obj, full=True)
@@ -127,7 +127,7 @@ def _get_nested_schema(obj, evalable=False, skip_hidden=False):
127
127
  if defs: schema['$defs'] = defs
128
128
  return schema
129
129
 
130
- # %% ../01_funccall.ipynb
130
+ # %% ../01_funccall.ipynb #748da965
131
131
  def get_schema(
132
132
  f:Union[callable,dict], # Function to get schema for
133
133
  pname='input_schema', # Key name for parameters
@@ -149,11 +149,11 @@ def get_schema(
149
149
  desc += f'\n\nReturns:\n- {ret_str}'
150
150
  return {"name": f.__name__, "description": desc, pname: schema}
151
151
 
152
- # %% ../01_funccall.ipynb
152
+ # %% ../01_funccall.ipynb #873000d7
153
153
  import ast, time, signal, traceback
154
154
  from fastcore.utils import *
155
155
 
156
- # %% ../01_funccall.ipynb
156
+ # %% ../01_funccall.ipynb #4703296a
157
157
  def _copy_loc(new, orig):
158
158
  "Copy location information from original node to new node and all children."
159
159
  new = ast.copy_location(new, orig)
@@ -162,7 +162,7 @@ def _copy_loc(new, orig):
162
162
  elif isinstance(o, list): setattr(new, field, [_copy_loc(value, orig) for value in o])
163
163
  return new
164
164
 
165
- # %% ../01_funccall.ipynb
165
+ # %% ../01_funccall.ipynb #1574585f
166
166
  def _run(code:str, glb:dict=None, loc:dict=None):
167
167
  "Run `code`, returning final expression (similar to IPython)"
168
168
  tree = ast.parse(code)
@@ -185,7 +185,7 @@ def _run(code:str, glb:dict=None, loc:dict=None):
185
185
  if _result is not None: return _result
186
186
  return stdout_buffer.getvalue().strip()
187
187
 
188
- # %% ../01_funccall.ipynb
188
+ # %% ../01_funccall.ipynb #81857615
189
189
  def python(
190
190
  code:str, # Code to execute
191
191
  glb:Optional[dict]=None, # Globals namespace
@@ -202,7 +202,7 @@ def python(
202
202
  except Exception as e: return traceback.format_exc()
203
203
  finally: signal.alarm(0)
204
204
 
205
- # %% ../01_funccall.ipynb
205
+ # %% ../01_funccall.ipynb #782c4415
206
206
  def mk_ns(fs):
207
207
  if isinstance(fs, abc.Mapping): return fs
208
208
  merged = {}
@@ -211,7 +211,7 @@ def mk_ns(fs):
211
211
  elif callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
212
212
  return merged
213
213
 
214
- # %% ../01_funccall.ipynb
214
+ # %% ../01_funccall.ipynb #6ef23838
215
215
  def _coerce_inputs(func, inputs):
216
216
  "Coerce inputs based on function type annotations"
217
217
  hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
@@ -223,7 +223,7 @@ def _coerce_inputs(func, inputs):
223
223
  else: res[k] = v
224
224
  return res
225
225
 
226
- # %% ../01_funccall.ipynb
226
+ # %% ../01_funccall.ipynb #d817ab01
227
227
  def call_func(fc_name, fc_inputs, ns, raise_on_err=True):
228
228
  "Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
229
229
  if not isinstance(ns, abc.Mapping): ns = mk_ns(ns)
@@ -235,7 +235,7 @@ def call_func(fc_name, fc_inputs, ns, raise_on_err=True):
235
235
  if raise_on_err: raise e from None
236
236
  else: return traceback.format_exc()
237
237
 
238
- # %% ../01_funccall.ipynb
238
+ # %% ../01_funccall.ipynb #7ac04e80-7bb9-4b52-8285-454684605d47
239
239
  async def call_func_async(fc_name, fc_inputs, ns, raise_on_err=True):
240
240
  "Awaits the function `fc_name` with the given `fc_inputs` using namespace `ns`."
241
241
  res = call_func(fc_name, fc_inputs, ns, raise_on_err=raise_on_err)
@@ -246,7 +246,7 @@ async def call_func_async(fc_name, fc_inputs, ns, raise_on_err=True):
246
246
  else: return traceback.format_exc()
247
247
  return res
248
248
 
249
- # %% ../01_funccall.ipynb
249
+ # %% ../01_funccall.ipynb #ede7ea66
250
250
  def mk_param(nm, props, req):
251
251
  "Create a `Parameter` for `nm` with schema `props`"
252
252
  kind = Parameter.POSITIONAL_OR_KEYWORD if nm in req else Parameter.KEYWORD_ONLY
@@ -257,14 +257,14 @@ def mk_param(nm, props, req):
257
257
  else: anno = type_map.get(props.get('type'), Any)
258
258
  return Parameter(nm, kind, default=default, annotation=anno)
259
259
 
260
- # %% ../01_funccall.ipynb
260
+ # %% ../01_funccall.ipynb #a8befff6
261
261
  def schema2sig(tool):
262
262
  "Convert json schema `tool` to a `Signature`"
263
263
  props, req = tool.inputSchema['properties'], tool.inputSchema.get('required', [])
264
264
  params = sorted([mk_param(k, v, req) for k, v in props.items()], key=lambda p: p.kind)
265
265
  return Signature(params)
266
266
 
267
- # %% ../01_funccall.ipynb
267
+ # %% ../01_funccall.ipynb #bb16561a
268
268
  def mk_tool(dispfn, tool):
269
269
  "Create a callable function from a JSON schema tool definition"
270
270
  sig = schema2sig(tool)
@@ -0,0 +1,252 @@
1
+ """LLM tools for inspecting the symbol table and importing modules"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../05_inspecttools.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['importmodule', 'SymbolNotFound', 'resolve', 'symsrc', 'symtype', 'symval', 'symtype_val', 'symdir', 'symnth',
7
+ 'symlen', 'symslice', 'symsearch', 'symset', 'symfiles_folder', 'symfiles_package']
8
+
9
+ # %% ../05_inspecttools.ipynb #5ae7ad05
10
+ from fastcore.utils import *
11
+ from fastcore.meta import delegates
12
+ import inspect, re, sys, ast, builtins, os, linecache
13
+ from importlib import import_module
14
+
15
+ from .xml import *
16
+
17
+ # %% ../05_inspecttools.ipynb #9778fca8
18
+ def _find_frame_dict(var:str):
19
+ "Find the dict (globals or locals) containing var"
20
+ frame = inspect.currentframe().f_back
21
+ while frame:
22
+ if var in frame.f_globals: return frame.f_globals
23
+ frame = frame.f_back
24
+ raise ValueError(f"Could not find {var} in any scope")
25
+
26
+ # %% ../05_inspecttools.ipynb #7fd415fa
27
+ @llmtool
28
+ def importmodule(
29
+ mod: str, # The module to import (e.g. 'torch.nn.functional')
30
+ caller_symbol:str = '__msg_id' # The name of the special variable to find the correct caller namespace
31
+ ):
32
+ """Import a module into the caller's global namespace so it's available for `symsrc`, `symval`, `symdir`, etc.
33
+ Use this before inspecting or using symbols from modules not yet imported."""
34
+ g = _find_frame_dict(caller_symbol)
35
+ import_module(mod)
36
+ g[mod.split('.')[0]] = import_module(mod.split('.')[0])
37
+
38
+ # %% ../05_inspecttools.ipynb #e40f68c9
39
+ class SymbolNotFound(Exception):
40
+ def __repr__(self): return f"SymbolNotFound({self.args[0]})"
41
+ __str__ = __repr__
42
+
43
+ _last = None
44
+
45
+ def resolve(
46
+ sym: str # Dotted symbol path, with optional [n] indexing, e.g. "module.attr.subattr[1]" or "_last" for previous result
47
+ ):
48
+ """Resolve a dotted symbol string to its Python object, with optional [n] indexing.
49
+ Sets global `_last` to the resolved object for chaining.
50
+ Pass `"_last"` to reference the result of the previous tool call.
51
+
52
+ Examples:
53
+
54
+ - `resolve("sympy.sets.sets.Interval")` -> `<class 'sympy.sets.sets.Interval'>`
55
+ - `resolve("mylist[2]")` -> third element of mylist"""
56
+ global _last
57
+ if (sym := sym.strip()) == '_last': return _last
58
+ g = _find_frame_dict('__msg_id')
59
+ if match := re.match(r'^(\w+)\[(\d+)\]$', sym):
60
+ attr, idx = match.groups()
61
+ parts, _last = ['_last'], _last[int(idx)] if attr == '_last' else g[attr][int(idx)]
62
+ else: parts = re.split(r'\.(?![^\[]*\])', sym)
63
+ try: obj = _last if parts[0] == '_last' else g[parts[0]]
64
+ except KeyError: raise SymbolNotFound(f"Symbol '{parts[0]}' not found. Consider using `importmodule` first.")
65
+ for part in parts[1:]:
66
+ match = re.match(r'(\w+)\[(\d+)\]$', part)
67
+ if match:
68
+ attr, idx = match.groups()
69
+ obj = getattr(obj, attr)[int(idx)]
70
+ else: obj = getattr(obj, part)
71
+ _last = obj
72
+ return obj
73
+
74
+ # %% ../05_inspecttools.ipynb #659bf879
75
+ def _src_from_lines(lines, start):
76
+ "Extract a single definition from lines starting at start (0-indexed)"
77
+ src = ''.join(lines[start:])
78
+ try: tree = ast.parse(src)
79
+ except SyntaxError: return None
80
+ if not tree.body: return None
81
+ return ''.join(lines[start:start + tree.body[0].end_lineno])
82
+
83
+ # %% ../05_inspecttools.ipynb #41ecbd5c
84
+ @llmtool
85
+ def symsrc(
86
+ sym: str # Dotted symbol path (e.g `Interval` or `sympy.sets.sets.Interval`) or "_last" for previous result
87
+ ):
88
+ """Get the source code for a symbol.
89
+
90
+ Examples:
91
+
92
+ - `symsrc("Interval")` -> source code of Interval class if it's already imported
93
+ - `symsrc("sympy.sets.sets.Interval")` -> source code of Interval class
94
+ - `symsrc("_last")` -> source of object from previous tool call
95
+ - For dispatchers or registries of callables: `symnth("module.dispatcher.funcs", n) then symsrc("_last")`"""
96
+ try: obj = resolve(sym)
97
+ except SymbolNotFound as e: return str(e)
98
+ if isinstance(obj, type) or callable(obj): pass
99
+ elif hasattr(obj, '__module__') and not inspect.ismodule(obj): obj = obj.__class__
100
+ try: fname = inspect.getfile(obj)
101
+ except (OSError, TypeError): fname = "<session>"
102
+ try: return f"File: {fname}\n\n{inspect.getsource(obj)}"
103
+ except (OSError, TypeError): pass
104
+ name = getattr(obj, '__name__', None)
105
+ if not name: raise OSError(f"Cannot get source for {sym}")
106
+ pat = rf'^(class|def)\s+{name}\b'
107
+ for fname, (_, _, lines, _) in linecache.cache.items():
108
+ src = ''.join(lines)
109
+ if match := re.search(pat, src, re.MULTILINE):
110
+ start = src[:match.start()].count('\n')
111
+ if extracted := _src_from_lines(lines, start): return f"File: {fname}\n\n{extracted}"
112
+ raise OSError(f"Source for {name} not found")
113
+
114
+ # %% ../05_inspecttools.ipynb #bbf67405
115
+ @llmtool
116
+ def symtype(
117
+ syms: str # Comma separated str list of dotted symbol paths (e.g `'Interval,a'` or `'sympy.sets.sets.Interval'`); "_last" for prev result
118
+ ):
119
+ """Get the type of a symbol and set `_last`.
120
+
121
+ Examples:
122
+
123
+ - `symtype("sympy.sets.sets.Interval")` -> `<class 'type'>`
124
+ - `symtype("doesnotexist")` -> `'SymbolNotFound`
125
+ - `symtype("_last")` -> type of previous result"""
126
+ def f(o):
127
+ try: return type(resolve(o))
128
+ except SymbolNotFound as e: return str(e)
129
+ return [f(o) for o in re.split(r'\,\s*', syms)]
130
+
131
+ # %% ../05_inspecttools.ipynb #dd4279b8
132
+ @llmtool
133
+ def symval(
134
+ syms: str # Comma separated str list of dotted symbol paths (e.g `Interval` or `sympy.sets.sets.Interval`); "_last" for prev result
135
+ ):
136
+ """List of repr of symbols' values.
137
+
138
+ Examples:
139
+
140
+ - `symval("sympy.sets.sets.Interval")` -> `[<class 'sympy.sets.sets.Interval'>]`
141
+ - `symval("some_dict.keys")` -> `[dict_keys([...])]`
142
+ - `symval("a,notexist")` -> `['foo','SymbolNotFound']`"""
143
+ def f(o):
144
+ try: return repr(resolve(o))
145
+ except SymbolNotFound as e: return str(e)
146
+ return [f(o) for o in re.split(r'\,\s*', syms)]
147
+
148
+ # %% ../05_inspecttools.ipynb #3cb05b78
149
+ def symtype_val(
150
+ syms: str # Comma separated str list of dotted symbol paths (e.g `Interval` or `sympy.sets.sets.Interval`); "_last" for prev result
151
+ ):
152
+ """List of 2-ple of (type,repr) of symbols' values.
153
+
154
+ Examples:
155
+
156
+ - `symtype_val("a,c,notexist")` -> `[(<class 'str'>,'foo'),(<class 'int'>,1), 'SymbolNotFound']`"""
157
+ def f(o):
158
+ try: r = resolve(o)
159
+ except SymbolNotFound as e: return 'SymbolNotFound'
160
+ return (type(r), repr(r))
161
+ return [f(o) for o in re.split(r'\,\s*', syms)]
162
+
163
+ # %% ../05_inspecttools.ipynb #1cd34596
164
+ @llmtool
165
+ def symdir(
166
+ sym: str, # Dotted symbol path (e.g `Interval` or `sympy.sets.sets.Interval`) or "_last" for previous result
167
+ exclude_private: bool=False # Filter out attrs starting with "_"
168
+ ):
169
+ """Get dir() listing of a symbol's attributes and set `_last`. E.g: `symdir("sympy.Interval")` -> `['__add__', '__and__', ...]`"""
170
+ res = dir(resolve(sym))
171
+ if not exclude_private: return res
172
+ return [o for o in res if o[0]!='_']
173
+
174
+ # %% ../05_inspecttools.ipynb #9542de0b
175
+ @llmtool
176
+ def symnth(
177
+ sym: str, # Dotted symbol path to a dict or object with .values()
178
+ n: int # Index into the values (0-based)
179
+ ):
180
+ """Get the nth value from a dict (or any object with .values()). Sets `_last` so you can chain with `symsrc("_last")` etc.
181
+
182
+ Examples:
183
+
184
+ - `symnth("dispatcher.funcs", 12)` -> 13th registered function
185
+ - `symnth("dispatcher.funcs", 0); symsrc("_last")` -> source of first handler"""
186
+ global _last
187
+ _last = list(resolve(sym).values())[n]
188
+ return _last
189
+
190
+ # %% ../05_inspecttools.ipynb #4ac6ca2d
191
+ @llmtool
192
+ def symlen(
193
+ sym: str # Dotted symbol path or "_last" for previous result
194
+ ):
195
+ "Returns the length of the given symbol"
196
+ return len(resolve(sym))
197
+
198
+ # %% ../05_inspecttools.ipynb #a5dfbe8f
199
+ @llmtool
200
+ def symslice(
201
+ sym: str, # Dotted symbol path or "_last" for previous result
202
+ start: int, # Starting index for slice
203
+ end: int # Ending index for slice
204
+ ):
205
+ "Returns the contents of the symbol from the given start to the end."
206
+ try: return resolve(sym)[start:end]
207
+ except Exception as e: return f'Error: {e}'
208
+
209
+ # %% ../05_inspecttools.ipynb #5fca4d70
210
+ @llmtool
211
+ def symsearch(
212
+ sym:str, # Dotted symbol path or "_last" for previous result
213
+ term:str, # Search term (exact string or regex pattern)
214
+ regex:bool=True, # If True, regex search; if False, exact match
215
+ flags:int=0 # Regex flags (e.g., re.IGNORECASE)
216
+ ):
217
+ """Search contents of symbol, which is assumed to be str for regex, or iterable for non-regex.
218
+ Regex mode returns (match, start, end) tuples; otherwise returns (item, index) tuples"""
219
+ if regex: return str([(m.group(), m.start(), m.end()) for m in re.finditer(term, resolve(sym), flags)])
220
+ else: return str([(x, i) for i, x in enumerate(resolve(sym)) if x == term])
221
+
222
+ # %% ../05_inspecttools.ipynb #02c09e1a
223
+ @llmtool
224
+ def symset(
225
+ val: str # Value to assign to _ai_sym
226
+ ):
227
+ "Set _ai_sym to the given value"
228
+ _find_frame_dict('__msg_id')['_ai_sym'] = val
229
+
230
+ # %% ../05_inspecttools.ipynb #72e5f0a8
231
+ @llmtool
232
+ @delegates(sym2folderctx)
233
+ def symfiles_folder(
234
+ sym:str, # Dotted symbol path or "_last" for previous result
235
+ **kwargs
236
+ ):
237
+ "Return XML context of files in the folder containing `sym`'s definition"
238
+ try: s = resolve(sym)
239
+ except SymbolNotFound as e: return str(e)
240
+ return sym2folderctx(s, **kwargs)
241
+
242
+ # %% ../05_inspecttools.ipynb #fdd0990e
243
+ @llmtool
244
+ @delegates(sym2pkgctx)
245
+ def symfiles_package(
246
+ sym:str, # Dotted symbol path or "_last" for previous result
247
+ **kwargs
248
+ ):
249
+ "Return XML context of all files in `sym`'s top-level package"
250
+ try: s = resolve(sym)
251
+ except SymbolNotFound as e: return str(e)
252
+ return sym2pkgctx(s, **kwargs)
@@ -1,20 +1,20 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../02_shell.ipynb.
2
2
 
3
- # %% auto 0
3
+ # %% auto #0
4
4
  __all__ = ['get_shell']
5
5
 
6
- # %% ../02_shell.ipynb
6
+ # %% ../02_shell.ipynb #1328ef69
7
7
  import ast, time, signal, traceback
8
8
  from fastcore.utils import *
9
9
 
10
- # %% ../02_shell.ipynb
10
+ # %% ../02_shell.ipynb #6bbf062d
11
11
  from IPython.terminal.interactiveshell import TerminalInteractiveShell
12
12
  from IPython.utils.capture import capture_output
13
13
 
14
- # %% ../02_shell.ipynb
14
+ # %% ../02_shell.ipynb #34099c2f
15
15
  TerminalInteractiveShell.orig_run = TerminalInteractiveShell.run_cell
16
16
 
17
- # %% ../02_shell.ipynb
17
+ # %% ../02_shell.ipynb #d6aa8e7b
18
18
  @patch
19
19
  def run_cell(self:TerminalInteractiveShell, cell, timeout=None):
20
20
  "Wrapper for original `run_cell` which adds timeout and output capture"
@@ -31,7 +31,7 @@ def run_cell(self:TerminalInteractiveShell, cell, timeout=None):
31
31
  finally:
32
32
  if timeout: signal.alarm(0)
33
33
 
34
- # %% ../02_shell.ipynb
34
+ # %% ../02_shell.ipynb #cdadbb12
35
35
  def get_shell()->TerminalInteractiveShell:
36
36
  "Get a `TerminalInteractiveShell` with minimal functionality"
37
37
  sh = TerminalInteractiveShell()
@@ -1,11 +1,11 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../00_xml.ipynb.
2
2
 
3
- # %% auto 0
4
- __all__ = ['doctype', 'json_to_xml', 'get_mime_text', 'cell2out', 'cell2xml', 'cells2xml', 'nb2xml', 'get_docstring', 'py2sigs',
5
- 'mk_doctype', 'mk_doc', 'docs_xml', 'read_file', 'files2ctx', 'folder2ctx', 'sym2file', 'sym2folderctx',
6
- 'sym2pkgpath', 'sym2pkgctx', 'folder2ctx_cli', 'parse_gh_url', 'repo2ctx', 'repo2ctx_cli']
3
+ # %% auto #0
4
+ __all__ = ['doctype', 'json_to_xml', 'get_mime_text', 'cell2out', 'cell2xml', 'cells2xml', 'nb2xml', 'mk_doctype', 'mk_doc',
5
+ 'docs_xml', 'read_file', 'files2ctx', 'folder2ctx', 'sym2file', 'sym2folderctx', 'sym2pkgpath', 'sym2pkgctx',
6
+ 'folder2ctx_cli', 'parse_gh_url', 'repo2ctx', 'repo2ctx_cli']
7
7
 
8
- # %% ../00_xml.ipynb
8
+ # %% ../00_xml.ipynb #033c76fd
9
9
  import hashlib, inspect, xml.etree.ElementTree as ET, ast
10
10
  from collections import namedtuple
11
11
  from ghapi.all import GhApi
@@ -16,7 +16,9 @@ from fastcore.xtras import hl_md
16
16
  from fastcore.xml import to_xml, Document, Documents, Document_content, Src, Source,Out,Outs,Cell,Notebook,Md,Code,Raw
17
17
  from fastcore.script import call_parse
18
18
 
19
- # %% ../00_xml.ipynb
19
+ from codesigs import file_sigs
20
+
21
+ # %% ../00_xml.ipynb #2795f9fc
20
22
  def json_to_xml(d:dict, # JSON dictionary to convert
21
23
  rnm:str # Root name
22
24
  )->str:
@@ -32,13 +34,13 @@ def json_to_xml(d:dict, # JSON dictionary to convert
32
34
  ET.indent(root)
33
35
  return ET.tostring(root, encoding='unicode')
34
36
 
35
- # %% ../00_xml.ipynb
37
+ # %% ../00_xml.ipynb #338af2a5
36
38
  def get_mime_text(data):
37
39
  "Get text from MIME bundle, preferring markdown over plain"
38
40
  if 'text/markdown' in data: return ''.join(list(data['text/markdown']))
39
41
  if 'text/plain' in data: return ''.join(list(data['text/plain']))
40
42
 
41
- # %% ../00_xml.ipynb
43
+ # %% ../00_xml.ipynb #aff831f8
42
44
  def cell2out(o):
43
45
  "Convert single notebook output to XML format"
44
46
  if hasattr(o, 'data'):
@@ -49,7 +51,7 @@ def cell2out(o):
49
51
  return Out(txt, type='stream', name=o.get('name', 'stdout'))
50
52
  if hasattr(o, 'ename'): return Out(f"{o.ename}: {o.evalue}", type='error')
51
53
 
52
- # %% ../00_xml.ipynb
54
+ # %% ../00_xml.ipynb #ac3ed87b
53
55
  _ctfuns = {'code': Code, 'markdown': Md, 'raw': Raw}
54
56
 
55
57
  def cell2xml(cell, out=True, ids=True, nums=False):
@@ -64,7 +66,7 @@ def cell2xml(cell, out=True, ids=True, nums=False):
64
66
  if out_items: parts.append(Outs(*out_items))
65
67
  return f(*parts, **kw)
66
68
 
67
- # %% ../00_xml.ipynb
69
+ # %% ../00_xml.ipynb #f780f576
68
70
  @delegates(cell2xml)
69
71
  def cells2xml(cells, wrap=Notebook, **kwargs):
70
72
  "Convert notebook to XML format"
@@ -78,32 +80,10 @@ def nb2xml(fname=None, nb=None, **kwargs):
78
80
  if not nb: nb = dict2obj(fname.read_json())
79
81
  return cells2xml(nb.cells, **kwargs)
80
82
 
81
- # %% ../00_xml.ipynb
82
- def get_docstring(node, lines):
83
- "Get docstring from source lines if present"
84
- if not (node.body and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant)): return None
85
- doc_node = node.body[0]
86
- return '\n'.join(lines[doc_node.lineno-1:doc_node.end_lineno])
87
-
88
- def py2sigs(fname=None, src=None):
89
- "Return signature+docstring text for all functions and class methods in source"
90
- if fname: src = Path(fname).expanduser().read_text()
91
- tree = ast.parse(src)
92
- lines = src.splitlines()
93
- res = []
94
- for node in ast.walk(tree):
95
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
96
- body_start = max(node.body[0].lineno - 1, node.lineno)
97
- sig = '\n'.join(lines[node.lineno-1:body_start])
98
- doc = get_docstring(node, lines)
99
- cts = f"{sig}\n{doc}" if doc else sig
100
- res.append(cts.strip('\r\n'))
101
- return '\n\n'.join(res)
102
-
103
- # %% ../00_xml.ipynb
83
+ # %% ../00_xml.ipynb #a01dc320
104
84
  doctype = namedtuple('doctype', ['src', 'content'])
105
85
 
106
- # %% ../00_xml.ipynb
86
+ # %% ../00_xml.ipynb #ce853491
107
87
  def _add_nls(s):
108
88
  "Add newlines to start and end of `s` if missing"
109
89
  if not s: return s
@@ -111,7 +91,7 @@ def _add_nls(s):
111
91
  if s[-1]!='\n': s = s+'\n'
112
92
  return s
113
93
 
114
- # %% ../00_xml.ipynb
94
+ # %% ../00_xml.ipynb #932e8858
115
95
  def mk_doctype(content:str, # The document content
116
96
  src:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided
117
97
  ) -> namedtuple:
@@ -119,7 +99,7 @@ def mk_doctype(content:str, # The document content
119
99
  if src is None: src = hashlib.md5(content.encode()).hexdigest()[:8]
120
100
  return doctype(_add_nls(str(src).strip()), _add_nls(content.strip()))
121
101
 
122
- # %% ../00_xml.ipynb
102
+ # %% ../00_xml.ipynb #15e454db
123
103
  def mk_doc(index:int, # The document index
124
104
  content:str, # The document content
125
105
  src:Optional[str]=None, # URL, filename, etc; defaults to `md5(content)` if not provided
@@ -131,7 +111,7 @@ def mk_doc(index:int, # The document index
131
111
  src = Src(NotStr(dt.src))
132
112
  return Document(src, content, index=index, **kwargs)
133
113
 
134
- # %% ../00_xml.ipynb
114
+ # %% ../00_xml.ipynb #32237f0a
135
115
  def docs_xml(docs:list[str], # The content of each document
136
116
  srcs:Optional[list]=None, # URLs, filenames, etc; each one defaults to `md5(content)` if not provided
137
117
  prefix:bool=False, # Include Anthropic's suggested prose intro?
@@ -146,18 +126,20 @@ def docs_xml(docs:list[str], # The content of each document
146
126
  kw = dict(title=title) if title else {}
147
127
  return pre + to_xml(Documents(*docs, **kw), do_escape=False)
148
128
 
149
- # %% ../00_xml.ipynb
129
+ # %% ../00_xml.ipynb #278b484b
150
130
  @delegates(nb2xml)
151
131
  def read_file(fname, max_size=None, sigs_only=False, **kwargs):
152
132
  "Read file content, converting notebooks to XML if needed"
153
133
  fname = Path(fname).expanduser()
154
134
  if fname.suffix == '.ipynb': res = nb2xml(fname, **kwargs)
155
- elif fname.suffix == '.py' and sigs_only: res = py2sigs(fname)
156
- else: res = fname.read_text()
135
+ elif sigs_only: res = '\n'.join(str(s) for s in file_sigs(fname))
136
+ else:
137
+ try: res = fname.read_text()
138
+ except UnicodeDecodeError: return f"[Skipped: {fname.name} is binary]"
157
139
  if max_size and len(res)>max_size: return f"[Skipped: {fname.name} exceeds {max_size} bytes]"
158
140
  return res
159
141
 
160
- # %% ../00_xml.ipynb
142
+ # %% ../00_xml.ipynb #7d92255e
161
143
  @delegates(docs_xml)
162
144
  def files2ctx(
163
145
  fnames:list[Union[str,Path]], # List of file names to add to context
@@ -166,7 +148,7 @@ def files2ctx(
166
148
  out:bool=True, # Include notebook cell outputs?
167
149
  ids:bool=True, # Include cell ids in notebooks?
168
150
  nums:bool=False, # Include line numbers in notebook cell source?
169
- sigs_only:bool=False, # For .py files, only include signatures and docstrings
151
+ sigs_only:bool=False, # Only include signatures and docstrings (where supported by `codesigs` lib)
170
152
  **kwargs
171
153
  )->str: # XML for LM context
172
154
  "Convert files to XML context, handling notebooks"
@@ -174,7 +156,7 @@ def files2ctx(
174
156
  contents = [read_file(o, max_size=max_size, out=out, ids=ids, sigs_only=sigs_only, nums=nums) for o in fnames]
175
157
  return docs_xml(contents, srcs or fnames, **kwargs)
176
158
 
177
- # %% ../00_xml.ipynb
159
+ # %% ../00_xml.ipynb #f97bc488
178
160
  @delegates(globtastic, but='func')
179
161
  def folder2ctx(
180
162
  path:Union[str,Path], # Folder to read
@@ -186,7 +168,7 @@ def folder2ctx(
186
168
  max_total:int=10_000_000, # Max total output size in bytes
187
169
  readme_first:bool=False, # Prioritize README files at start of context?
188
170
  files_only:bool=False, # Return dict of {filename: size} instead of context?
189
- sigs_only:bool=False, # Return signatures instead of full text for python files?
171
+ sigs_only:bool=False, # Return signatures instead of full text? (where supported by `codesigs` lib)
190
172
  ids:bool=True, # Include cell ids in notebooks?
191
173
  **kwargs
192
174
  )->Union[str,dict]:
@@ -201,35 +183,43 @@ def folder2ctx(
201
183
  if max_total and len(res) > max_total: res = truncstr(res, max_total, suf=suf, sizevar='_outsz_')
202
184
  return res
203
185
 
204
- # %% ../00_xml.ipynb
186
+ # %% ../00_xml.ipynb #9dc68935
205
187
  def sym2file(sym):
206
188
  "Return md string with filepath and contents for a symbol's source file"
207
189
  f = Path(inspect.getfile(sym))
208
190
  return f"- `{f}`\n\n````\n{f.read_text()}\n````"
209
191
 
210
- # %% ../00_xml.ipynb
192
+ # %% ../00_xml.ipynb #daef58e8
211
193
  @delegates(folder2ctx)
212
194
  def sym2folderctx(
213
195
  sym,
214
- types:str|list='py', # list or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
215
- skip_file_re=r'^_mod',
216
- **kwargs):
196
+ types:str|list='py', # List or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
197
+ skip_file_re:str=r'^_mod', # Skip files matching regex
198
+ **kwargs
199
+ ):
217
200
  "Return folder context for a symbol's source file location"
218
201
  return folder2ctx(Path(inspect.getfile(sym)).parent, types=types, skip_file_re=skip_file_re, **kwargs)
219
202
 
220
- # %% ../00_xml.ipynb
203
+ # %% ../00_xml.ipynb #9afa301c
221
204
  def sym2pkgpath(sym):
222
205
  "Get root package path for a symbol"
223
- root = sym.__module__.split('.')[0]
206
+ mod = sym.__name__ if inspect.ismodule(sym) else sym.__module__
207
+ root = mod.split('.')[0]
224
208
  return Path(sys.modules[root].__path__[0])
225
209
 
226
- # %% ../00_xml.ipynb
210
+ # %% ../00_xml.ipynb #798ca61f
227
211
  @delegates(folder2ctx)
228
- def sym2pkgctx(sym, types:str|list='py', skip_file_re=r'^_mod', **kwargs):
229
- "Return repo context for a symbol's root package"
230
- return folder2ctx(sym2pkgpath(sym), types=types, skip_file_re=skip_file_re, **kwargs)
212
+ def sym2pkgctx(
213
+ sym,
214
+ types:str|list='py', # List or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
215
+ skip_file_re:str=r'^_mod', # Skip files matching regex
216
+ skip_folder_re:str=r'^(\.|__)', # Skip folders matching regex
217
+ **kwargs
218
+ ):
219
+ "Return contents of files in a symbol's root package"
220
+ return folder2ctx(sym2pkgpath(sym), types=types, skip_file_re=skip_file_re, skip_folder_re=skip_folder_re, **kwargs)
231
221
 
232
- # %% ../00_xml.ipynb
222
+ # %% ../00_xml.ipynb #6a786c55
233
223
  @call_parse
234
224
  @delegates(folder2ctx)
235
225
  def folder2ctx_cli(
@@ -240,13 +230,13 @@ def folder2ctx_cli(
240
230
  "CLI to convert folder contents to XML context, handling notebooks"
241
231
  print(folder2ctx(path, out=out, **kwargs))
242
232
 
243
- # %% ../00_xml.ipynb
233
+ # %% ../00_xml.ipynb #d10ed2c1
244
234
  def parse_gh_url(url):
245
235
  "Parse GitHub URL into (owner, repo, type, ref, path) or None"
246
236
  m = re.match(r'https?://(?:www\.)?github\.com/([^/]+)/([^/]+)(?:/([^/]+)(?:/([^/]+)(?:/(.+))?)?)?', url)
247
237
  return dict(zip('owner repo typ ref path'.split(), m.groups())) if m else None
248
238
 
249
- # %% ../00_xml.ipynb
239
+ # %% ../00_xml.ipynb #d91934db
250
240
  @delegates(folder2ctx)
251
241
  def repo2ctx(
252
242
  owner:str, # GitHub repo owner or "owner/repo" or a full github URL
@@ -281,7 +271,7 @@ def repo2ctx(
281
271
  if folder: subdir = subdir/folder
282
272
  return folder2ctx(subdir, include_base=False, title=title, readme_first=True, **kwargs)
283
273
 
284
- # %% ../00_xml.ipynb
274
+ # %% ../00_xml.ipynb #7654e554
285
275
  @call_parse
286
276
  @delegates(repo2ctx, but='include_base,title,readme_first')
287
277
  def repo2ctx_cli(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolslm
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Tools to make language models a bit easier to use
5
5
  Home-page: https://github.com/AnswerDotAI/toolslm
6
6
  Author: Jeremy Howard
@@ -16,10 +16,12 @@ Classifier: License :: OSI Approved :: Apache Software License
16
16
  Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
- Requires-Dist: fastcore>=1.9.7
19
+ Requires-Dist: fastcore>=1.12.4
20
20
  Requires-Dist: httpx
21
21
  Requires-Dist: ghapi
22
+ Requires-Dist: codesigs
22
23
  Provides-Extra: dev
24
+ Requires-Dist: ipython; extra == "dev"
23
25
  Dynamic: author
24
26
  Dynamic: author-email
25
27
  Dynamic: classifier
@@ -8,6 +8,7 @@ toolslm/__init__.py
8
8
  toolslm/_modidx.py
9
9
  toolslm/download.py
10
10
  toolslm/funccall.py
11
+ toolslm/inspecttools.py
11
12
  toolslm/md_hier.py
12
13
  toolslm/shell.py
13
14
  toolslm/xml.py
@@ -0,0 +1,7 @@
1
+ fastcore>=1.12.4
2
+ httpx
3
+ ghapi
4
+ codesigs
5
+
6
+ [dev]
7
+ ipython
@@ -1 +0,0 @@
1
- __version__ = "0.3.24"
@@ -1,5 +0,0 @@
1
- fastcore>=1.9.7
2
- httpx
3
- ghapi
4
-
5
- [dev]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes