-
Notifications
You must be signed in to change notification settings - Fork 7
Feature api #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ascottDI
wants to merge
4
commits into
main
Choose a base branch
from
feature-API
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Feature api #115
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8a57872
Creating initial framework and first pass of development
0177aac
added functionality that each module will declare its own meta data o…
955b4bb
added getapimeta so di.torq can collect and centrally register each m…
bf376aa
simplified the getapi data framework to make it less intrusive and bu…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # di.api | ||
|
|
||
| Registration and metadata for a process's public API functions. Each entry describes one callable | ||
| function — its name, whether it is public, and human-readable description / parameters / return | ||
| text. The registry is the single source of truth (there is no live-namespace scan, because module | ||
| code does not live in scannable root namespaces). | ||
|
|
||
| **Registration model:** di.torq collects the api metadata from each module at startup and registers | ||
| it here centrally via `add`. di.api has no dependency on other modules, and other modules do not | ||
| depend on di.api. | ||
|
|
||
| > **Status: v1 complete.** Registry (`add`/`getapi`) and query surface (`find`/`f`/`p`) are in | ||
| > place. The live-namespace-introspection tools from TorQ's `.api` (`search`, `whereami`, `mem`, | ||
| > `fullapi`'s namespace scan, `exportconfig`) are intentionally **not** ported — see "Not ported". | ||
|
|
||
| ## Initialisation & Dependencies | ||
|
|
||
| `init` must be called before any other function. The `log` dependency is **required** — there is no | ||
| fallback, and the module does no adaptation. The `log` value must already be a binary | ||
| `` `info`warn`error!{[c;m]} `` dict (context symbol, message string), built from `di.log` or | ||
| hand-rolled; a raw monadic `kx.log` instance must be wrapped by the caller first. | ||
|
|
||
| ```q | ||
| api:use`di.api | ||
| logger:use`di.log | ||
| api.init[enlist[`log]!enlist `info`warn`error!(logger.info;logger.warn;logger.error)] | ||
| ``` | ||
|
|
||
| | Dependency | Key | Required | Description | | ||
| |---|---|---|---| | ||
| | logger | `` `log `` | yes | Functions `info`,`warn`,`error` — each binary `{[c;m] ...}` (context symbol, message string) | | ||
|
|
||
| ## Exported functions | ||
|
|
||
| | Function | Signature | Description | | ||
| |---|---|---| | ||
| | `init` | `init[deps]` | Wire the injected logger and start with an empty registry. Errors immediately if the `log` dependency is missing or malformed. | | ||
| | `add` | `add[name;public;descrip;params;return]` | Register (or overwrite) one api entry. `name` is a symbol (the registry key), `public` a boolean; `descrip`/`params`/`return` are description text. Re-adding the same `name` updates it in place. | | ||
| | `getapi` | `getapi[]` | Return the full registry as a keyed table (keyed on `name`, columns `public`/`descrip`/`params`/`return`). | | ||
| | `find` | `find[s;p]` | Return registry entries whose name matches pattern `s` (a symbol — `` ` `` matches all, else a `*s*` substring — or a string glob) and whose `public` flag is in `p` (`1b` public-only, `01b` all). Case-insensitive; returns an unkeyed table. | | ||
| | `f` | `f[s]` | `find[s;01b]` — all matching entries, public and non-public. | | ||
| | `p` | `p[s]` | `find[s;1b]` — public matching entries only. | | ||
| | `getapimeta` | `getapimeta[]` | Return **this module's own** api metadata — one `(name;public;descrip;params;return)` row per exported function — for di.torq to collect and register. Every module exposes this (the module-owned metadata convention). Names are bare (the module's own); di.torq applies process-wide qualification when registering. | | ||
|
|
||
| ### Registry schema | ||
|
|
||
| ```q | ||
| ([name:`u#`symbol$()] public:`boolean$(); descrip:(); params:(); return:()) | ||
| ``` | ||
|
|
||
| ## Design decisions | ||
|
|
||
| - **Registry-only, no live scan.** The core departure from TorQ. Module functions live in private | ||
| `.z.m`, invisible to `\v`/`\f`, so a live-namespace scan would report a process's top-level glue as | ||
| its API, not the module logic. The registry is populated explicitly instead and is the single | ||
| source of truth (see "Not ported" for the TorQ functions this drops). | ||
| - **Module-owned metadata (`getapimeta`).** Rather than a central hand-maintained list, each module | ||
| declares its own API as data — one `(name;public;descrip;params;return)` row per export — and | ||
| `di.torq` collects and registers them via `add`. Names in `getapimeta` are **bare** (the module's | ||
| own); `di.torq` applies process-wide qualification. `di.api` dog-foods this: `getapimeta` documents | ||
| its own seven exports, and a test asserts every `export` name has a matching row. | ||
| - **Metadata is a compact row literal, not resident data.** `getapimeta` is built with the | ||
| row-oriented `flip cols!flip(rows)` idiom — one self-contained line per function — rather than five | ||
| parallel column-lists, so it stays terse and a function can't drift out of column alignment. It | ||
| costs the module nothing at runtime: it's a *function*, materialised once when `di.torq` calls it | ||
| at startup, then registered centrally; the module keeps no resident copy. This is the form the | ||
| modularisation skill mandates for every module. | ||
| - **`find` unifies the query surface.** `f` and `p` are just projections `find[;01b]` / `find[;1b]`, | ||
| so there is one matching implementation. Symbol patterns become `*substring*`, `` ` `` matches all, | ||
| and strings pass through as globs — all case-insensitive. | ||
| - **Minimal, registered surface.** The public API is small and every entry is something `di.torq` | ||
| will actually register. TorQ's introspection helpers are dropped, not stubbed (below). | ||
|
|
||
| ## Not ported from TorQ's `.api` (deliberate) | ||
|
|
||
| TorQ's `.api` marries a registry with **live introspection of the root namespaces** (`\v`/`\f` scans, | ||
| grepping function definitions, sizing variables). That half does not fit the module world: module | ||
| code lives in each module's private `.z.m`, not in scannable root namespaces, so those tools would | ||
| see only a process's top-level glue, not the module logic they exist to inspect. Accordingly: | ||
|
|
||
| | TorQ function | Why not ported | | ||
| |---|---| | ||
| | `u` | "public, excluding `.q`/`.Q`/`.h`/`.o`" — only filtered primitives out of the live scan; this registry holds only registered module functions, so `u` == `p`. | | ||
| | `search` / `s` | Greps live function *definitions* across root namespaces — cannot see module-private code. | | ||
| | `whereami` | Reverse-looks-up a function value to its name via the root scan — returns nothing for a function that lives inside a module (i.e. the usual error-trap case). | | ||
| | `fullapi` (namespace scan) | The scan-and-left-join-`detail` model does not apply; `getapi`/`find` serve the registry directly. | | ||
| | `mem` / `m` | Memory sizing belongs to `di.memstats`. | | ||
| | `exportconfig` / `exportallconfig` / `torqnamespaces` | A faithful port needs config **values** (di.config `getmodule`) joined with **descriptions** (di.api `getapi`/`find`). di.config and di.api are both standalone and don't depend on each other, so the join belongs in **di.torq** (it has both). It is a **di.torq-era task**, not a di.config change: di.config's `getmodule` already returns per-namespace values; the missing piece is pairing them with the api descriptions across namespaces. | | ||
|
|
||
| ## Hard dependencies | ||
|
|
||
| None. | ||
|
|
||
| ## Tests | ||
|
|
||
| ```q | ||
| k4unit:use`di.k4unit | ||
| k4unit.moduletest`di.api | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| / api - a registry of a process's callable functions: one entry per function holding its name, | ||
| / public flag, and description / params / return text. registry-only (no live namespace scan - | ||
| / module code is not in root namespaces). di.torq registers each module's metadata here via add; | ||
| / di.api depends on no other module. public api: add, getapi, find/f/p. | ||
|
|
||
| / registry template (constant); .z.m.detail is the live copy | ||
| detailschema:([name:`u#`symbol$()] public:`boolean$(); descrip:(); params:(); return:()); | ||
|
|
||
| init:{[deps] | ||
| / wire the injected logger (required, no fallback) and start with an empty registry. deps: a `log | ||
| / key holding a binary `info`warn`error dict of {[c;m]} loggers (di.log or hand-rolled; a monadic | ||
| / kx.log instance must be wrapped first). e.g. di.api.init[enlist[`log]!enlist logdep] | ||
| if[99h<>type deps; | ||
| '"di.api: deps must be a dict with a `log key"]; | ||
| if[not `log in key deps; | ||
| '"di.api: log dependency is required; pass `info`warn`error functions keyed on `log"]; | ||
| if[99h<>type deps`log; | ||
| '"di.api: log value must be a dict of `info`warn`error functions"]; | ||
| if[not all (`info`warn`error) in key deps`log; | ||
| '"di.api: log dict must have `info`warn`error keys; got: ",(", " sv string key deps`log)]; | ||
| .z.m.loginfo:deps[`log]`info; | ||
| .z.m.logwarn:deps[`log]`warn; | ||
| .z.m.logerr:deps[`log]`error; | ||
| .z.m.detail:detailschema; | ||
| }; | ||
|
|
||
| add:{[name;public;descrip;params;return] | ||
| / register (or overwrite) one entry, keyed on name (re-adding a name updates in place). | ||
| / called by di.torq at startup for each module's exported functions. | ||
| if[not -11h=type name; | ||
| .z.m.logerr[`add;err:"di.api: name must be a symbol"]; | ||
| 'err; | ||
| ]; | ||
| if[not -1h=type public; | ||
| .z.m.logerr[`add;err:"di.api: public must be a boolean"]; | ||
| 'err; | ||
| ]; | ||
| .z.m.detail:.z.m.detail upsert (name;public;descrip;params;return); | ||
| }; | ||
|
|
||
| getapi:{[] | ||
| / return the whole registry (keyed table, keyed on name) | ||
| :.z.m.detail; | ||
| }; | ||
|
|
||
| find:{[s;p] | ||
| / registry entries whose name matches s and whose public flag is in p. s: a symbol (` = all, else | ||
| / a *s* substring) or a string glob; p: public flags to include (1b public-only, 01b all). | ||
| / case-insensitive; returns an unkeyed table. | ||
| if[-11h=type s;s:$[null s;"*";"*",string[s],"*"]]; | ||
| if[not 10h=abs type s:s,(); | ||
| .z.m.logerr[`find;err:"di.api: search pattern must be a symbol or string"]; | ||
| 'err; | ||
| ]; | ||
| :select from .z.m.detail where lower[string name] like lower s, public in p; | ||
| }; | ||
|
|
||
| / all entries (public and non-public) | ||
| f:find[;01b]; | ||
| / public entries only | ||
| p:find[;1b]; | ||
|
|
||
| getapimeta:{[] | ||
| / this module's api metadata, one row per exported function, for di.torq to collect and register | ||
| / with di.api. names are bare (the module's own); di.torq applies the process-wide qualification. | ||
| / one self-contained (name;public;descrip;params;return) row per line - flip cols!flip rows. | ||
| :flip `name`public`descrip`params`return!flip( | ||
| (`init; 0b; "wire the injected logger and start an empty registry"; "[dict: deps with a `log key]"; "null"); | ||
| (`add; 0b; "register (or overwrite) one api entry, keyed on name"; "[symbol: name; boolean: public; descrip; params; return]"; "null"); | ||
| (`getapi; 1b; "return the whole registry (keyed table)"; "[]"; "keyed table: the registry"); | ||
| (`find; 1b; "registry entries matching a name pattern and public flag"; "[symbol|string: name pattern; boolean(list): public flags]"; "table: matching entries"); | ||
| (`f; 1b; "find[;01b] - all entries matching a name pattern"; "[symbol|string: name pattern]"; "table: matching entries"); | ||
| (`p; 1b; "find[;1b] - public entries matching a name pattern"; "[symbol|string: name pattern]"; "table: matching public entries"); | ||
| (`getapimeta; 0b; "this module's api metadata rows"; "[]"; "table: metadata rows")); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| / api module - registration and metadata for a process's public API functions. | ||
| / registry-only (no live namespace scan); di.torq collects each module's getapimeta and registers here. | ||
| \l ::api.q | ||
| export:([init;add;getapi;find;f;p;getapimeta]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| action,ms,bytes,lang,code,repeat,minver,comment | ||
| before,0,0,q,api:use`di.api,1,1,load the module | ||
| before,0,0,q,mylog:`info`warn`error!({[c;m]};{[c;m]};{[c;m]}),1,1,define a no-op binary logger {[c;m]} | ||
| before,0,0,q,api.init[enlist[`log]!enlist mylog],1,1,init with the required log dependency | ||
| comment,,,,,,,init - dependency validation | ||
| run,0,0,q,api.init[enlist[`log]!enlist mylog],1,1,init succeeds with a valid log dependency | ||
| fail,0,0,q,api.init[(::)],1,1,init rejects a non-dictionary deps | ||
| fail,0,0,q,api.init[enlist[`other]!enlist mylog],1,1,init rejects deps without a log key | ||
| fail,0,0,q,api.init[enlist[`log]!enlist 42],1,1,init rejects a non-dict log value | ||
| fail,0,0,q,api.init[enlist[`log]!enlist(`info`warn!({[c;m]};{[c;m]}))],1,1,init rejects a log dict missing the error key | ||
| comment,,,,,,,add and getapi - registry | ||
| run,0,0,q,api.init[enlist[`log]!enlist mylog],1,1,re-init to reset the registry to empty | ||
| true,0,0,q,0=count api.getapi[],1,1,registry starts empty after init | ||
| run,0,0,q,"api.add[`.gw.asyncexec;1b;""Execute async"";""[query;servers]"";""result""]",1,1,register a public api entry | ||
| true,0,0,q,1=count api.getapi[],1,1,add stores one entry | ||
| true,0,0,q,1b~(api.getapi[])[`.gw.asyncexec]`public,1,1,public flag stored | ||
| true,0,0,q,"""Execute async""~(api.getapi[])[`.gw.asyncexec]`descrip",1,1,description stored | ||
| run,0,0,q,"api.add[`.gw.asyncexec;0b;""updated"";""[]"";""x""]",1,1,re-add the same name | ||
| true,0,0,q,1=count api.getapi[],1,1,re-adding the same name overwrites (no duplicate) | ||
| true,0,0,q,0b~(api.getapi[])[`.gw.asyncexec]`public,1,1,re-add updates the entry in place | ||
| fail,0,0,q,"api.add[""notasym"";1b;""d"";""p"";""r""]",1,1,add rejects a non-symbol name | ||
| fail,0,0,q,api.add[`.x.f;1;`d;`p;`r],1,1,add rejects a non-boolean public flag | ||
| comment,,,,,,,find / f / p - query the registry | ||
| run,0,0,q,api.init[enlist[`log]!enlist mylog],1,1,re-init to reset the registry | ||
| run,0,0,q,"api.add[`.gw.asyncexec;1b;""a"";""p"";""r""]",1,1,seed a public entry | ||
| run,0,0,q,"api.add[`.gw.syncexec;1b;""a"";""p"";""r""]",1,1,seed another public entry | ||
| run,0,0,q,"api.add[`.api.add;1b;""a"";""p"";""r""]",1,1,seed a public entry with a different name | ||
| run,0,0,q,"api.add[`.internal.helper;0b;""a"";""p"";""r""]",1,1,seed a non-public entry | ||
| true,0,0,q,4=count api.f`,1,1,f with a null symbol returns all entries | ||
| true,0,0,q,3=count api.p`,1,1,p returns only the public entries | ||
| true,0,0,q,2=count api.f`exec,1,1,f matches names containing exec (substring) | ||
| true,0,0,q,2=count api.f`EXEC,1,1,name matching is case-insensitive | ||
| true,0,0,q,"2=count api.f""*gw*""",1,1,f accepts a string glob pattern | ||
| true,0,0,q,`.internal.helper in exec name from api.f`helper,1,1,f includes non-public entries | ||
| true,0,0,q,0=count api.p`helper,1,1,p excludes the non-public helper | ||
| fail,0,0,q,api.find[42;01b],1,1,find rejects a pattern that is not a symbol or string | ||
| comment,,,,,,,getapimeta - module api metadata | ||
| true,0,0,q,(asc key api)~asc exec name from api.getapimeta[],1,1,getapimeta documents exactly the module's exports | ||
| true,0,0,q,`name`public`descrip`params`return~cols api.getapimeta[],1,1,getapimeta rows carry the registry columns | ||
| true,0,0,q,1b~(1!api.getapimeta[])[`find]`public,1,1,a discovery function (find) is marked public | ||
| true,0,0,q,0b~(1!api.getapimeta[])[`init]`public,1,1,framework plumbing (init) is not public |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
findfunction converts the search patternsto a list withs,()and then applieslikewith a scalar pattern. In q,likeexpects a scalar string pattern on the right-hand side, but afters:s,(),sis a list (e.g.enlist "*foo*"). A list pattern passed tolikewill produce a type error at runtime. The,()coercion should be removed; the scalar strings(already ensured to be a string by the preceding guards) should be passed directly tolike.