307 Commits

Author SHA1 Message Date
kolo de7972c14f update 2026-03-17 19:48:33 +03:00
kolo 688dec6591 new command info 2026-03-17 10:28:26 +03:00
kolo 8d68cdc40d update 2026-03-16 14:23:09 +03:00
kolo 2785779583 new command routes 2026-03-15 19:56:28 +03:00
kolo 0d8871a719 support module paths and __main__.py in entrypoint resolver 2026-03-15 19:17:16 +03:00
kolo 5eece75c40 benchs 2026-03-15 19:09:02 +03:00
kolo a3d7630219 cli module better 2026-03-15 18:06:45 +03:00
kolo 7ffc6cd987 new changelog fragment 2026-03-13 17:42:54 +03:00
kolo db94cc8c9e complete entrypoint resolver refactor 2026-03-13 17:29:14 +03:00
kolo b9b83540e2 perf boooooooooooooost 2026-03-13 16:50:38 +03:00
kolo 1cd5c3759e update 2026-03-13 12:17:32 +03:00
kolo 44f7b42302 update 2026-03-12 15:32:10 +03:00
kolo b2f5a1b163 new diagrams in benchmarks 2026-02-13 14:42:59 +03:00
kolo 1023d05419 Merge branch 'main' into cli 2026-02-12 14:19:50 +03:00
kolo 732a4456b7 ruff format 2026-02-12 14:18:53 +03:00
kolo a72ddf61b7 Merge pull request #12 from koloideal/dependabot/uv/pillow-12.1.1
Bump pillow from 12.1.0 to 12.1.1
2026-02-12 10:06:53 +03:00
dependabot[bot] eb5830ec1b Bump pillow from 12.1.0 to 12.1.1
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.0 to 12.1.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/12.1.0...12.1.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-12 07:06:25 +00:00
kolo de6d35205c rename orchestrator method start_polling to run_repl 2026-02-10 14:03:37 +03:00
kolo 6ed1d35e8a new command new) 2026-02-10 13:41:51 +03:00
kolo 18a8376469 add new init command for generate boilerplate in flat or src layout 2026-02-09 14:50:03 +03:00
kolo 1211518c40 fix ovelapping with argparser 2026-02-08 22:47:32 +03:00
kolo 70f1327a0d complete creating run command 2026-02-08 22:37:42 +03:00
kolo b732036e87 cli module creating 2026-02-08 19:23:15 +03:00
kolo e9dd7af905 start creating cli module 2026-02-07 13:31:23 +03:00
kolo da06099990 Merge pull request #8 from koloideal/benchmarks
Benchmarks
2026-02-07 01:30:03 +03:00
kolo 8ff9944d37 benchs 2026-02-07 01:27:03 +03:00
kolo c07ee92371 benchs 2026-02-07 01:24:37 +03:00
kolo a21570e779 benchs 2026-02-06 23:52:26 +03:00
kolo f1034ff447 benchs 2026-02-06 23:41:55 +03:00
kolo fcff6f4263 Update documentation 2026-02-02 22:23:03 +03:00
kolo 417e0e2905 benchs 2026-02-01 02:21:36 +03:00
kolo 31dc49a1bf fix tests 2026-02-01 02:00:54 +03:00
kolo f859451069 benchs 2026-02-01 01:18:27 +03:00
kolo 24aa75eb37 viewr 2026-02-01 00:26:25 +03:00
kolo 5f6b3368e1 extract presentation layer 2026-01-30 20:52:52 +03:00
kolo d03ce5061b benchs 2026-01-29 14:09:15 +03:00
kolo 107530a4b1 benchs 2026-01-29 02:22:48 +03:00
kolo 9aa99352fe fix tests 2026-01-28 14:52:41 +03:00
kolo 2a9281a421 benchs 2026-01-28 02:23:40 +03:00
kolo 0567a3f4a3 release generate 2026-01-25 16:04:47 +03:00
kolo dfe482c545 release generate 2026-01-25 16:01:34 +03:00
kolo 46c1ec02fd comm 2026-01-24 02:24:31 +03:00
kolo 70bbbd76ce benchs 2026-01-24 02:18:39 +03:00
kolo 19bbaab1ee diagrams 2026-01-24 02:06:46 +03:00
kolo 1c54f11f31 benchs 2026-01-24 00:04:28 +03:00
kolo 2ad86dbedd bench 2026-01-22 22:39:35 +03:00
kolo f27f7b135b Update documentation and code snippets 2026-01-22 22:02:19 +03:00
kolo f9a85da430 Merge branch 'main' into benchmarks 2026-01-22 05:01:16 +03:00
kolo 2f557b99b0 Merge pull request #10 from koloideal/dependabot/uv/urllib3-2.6.3
Bump urllib3 from 2.5.0 to 2.6.3
2026-01-22 05:00:14 +03:00
dependabot[bot] 63c4443ff6 Bump urllib3 from 2.5.0 to 2.6.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 01:59:06 +00:00
kolo 30761749b4 Merge branch 'main' into benchmarks 2026-01-22 04:56:27 +03:00
kolo 0c3048266a Merge pull request #9 from koloideal/prompt-toolkit
Prompt toolkit
2026-01-22 04:49:41 +03:00
kolo 088c1720c4 fix typechecker errors 2026-01-22 04:48:53 +03:00
kolo 838f33db87 remove pyreadline3 from dependencies 2026-01-22 04:31:22 +03:00
kolo b8e9fdcb9c Update documentation and code snippets 2026-01-22 04:29:13 +03:00
kolo 295e260a46 start work 2026-01-22 03:13:42 +03:00
kolo 3fa7b17de9 benchs 2026-01-22 01:59:03 +03:00
kolo a174e0d5ab bench 2026-01-19 00:29:24 +03:00
kolo 9d250fde9c bench 2026-01-18 23:53:52 +03:00
kolo e7d064908f bench 2026-01-18 18:41:03 +03:00
kolo ba9a7b5539 bbbbbbenchh 2026-01-18 02:30:13 +03:00
kolo 69e871a639 bbbbbbenchh 2026-01-18 01:59:55 +03:00
kolo 1648a8206a bbbbbbenchh 2026-01-17 23:43:59 +03:00
kolo 2e5b19f4d8 benchs 2026-01-17 01:21:39 +03:00
kolo 6aa6b0f179 benchs 2026-01-17 00:42:00 +03:00
kolo 00cb94ffe2 Merge pull request #7 from koloideal/improve-perf
Improve perf
2026-01-15 17:00:38 +03:00
kolo 285ea39fa9 ruff wtf 2026-01-15 16:59:57 +03:00
kolo 30e5fd6ebe perf 2026-01-15 16:41:50 +03:00
kolo 18f62d3e7c perffff 2026-01-15 14:52:41 +03:00
kolo 3cd74fc186 benchs 2026-01-15 03:02:41 +03:00
kolo 9bde1321e1 benchs 2026-01-15 02:49:12 +03:00
kolo 0f8b1c05fc pretty gifff 2026-01-15 02:02:50 +03:00
kolo cd3dd10d11 start build pretty benchmarks 2026-01-14 14:51:43 +03:00
kolo b8d8c44bdd start build pretty benchmarks 2026-01-14 14:40:47 +03:00
kolo 4957de95d3 Update documentation and code snippets 2026-01-13 22:18:57 +03:00
kolo c5dab43c87 fix tests and improve perf 2026-01-09 10:14:25 +03:00
kolo 56189be6ab better perf 2025-12-09 12:02:26 +03:00
kolo 725a1f2e40 perf 2025-12-08 21:49:46 +03:00
kolo 22970f7115 Update documentation 2025-12-08 19:53:03 +03:00
kolo 6598fb7fa6 Update documentation and code snippets 2025-12-08 19:30:14 +03:00
kolo 183f069766 Update documentation and code snippets 2025-12-08 19:29:54 +03:00
kolo 75b1efb259 better perf 2025-12-08 14:17:31 +03:00
kolo cb6549452d Merge pull request #6 from koloideal/better-tests
100% codebase test coverage
2025-12-07 01:55:04 +03:00
kolo 425342c059 new tests 2025-12-07 01:51:24 +03:00
kolo 9423034a08 better testsssssss 2025-12-07 01:33:39 +03:00
kolo dee328525d refactor system tests 2025-12-06 20:01:47 +03:00
kolo 1d2ab6f6bb refactor tests and add new 2025-12-06 11:55:50 +03:00
kolo a2ef2652ed refactor tests and add new 2025-12-06 09:26:53 +03:00
kolo 2423f57000 new tests, upgrade tests coverage 2025-12-05 22:57:19 +03:00
kolo 913e7f16ca refactor tests 2025-12-05 16:30:56 +03:00
kolo 20b638c421 refactor tests 2025-12-05 15:54:03 +03:00
kolo b0e6127c1e Update documentation 2025-12-05 10:03:34 +03:00
kolo 67bc8960e7 Merge branch 'docs/create_docs' 2025-12-04 22:13:59 +03:00
kolo 6b9cb586d5 Update documentation 2025-12-04 22:08:49 +03:00
kolo ce7e24b924 feat: impl docs (#4)
The entire public api is covered with documentation in two languages - Russian and English.

the library now supports the latest three versions of python - 3.12, 3.13 and 3.14

minor design changes: now, when a Boolean flag is entered, its value is an empty string, not None.

tests have been adapted to the supported versions of python, readmi has been redesigned in two languages, German is no longer available.
2025-12-04 21:55:19 +03:00
kolo 5c933ab656 Update documentation 2025-12-04 21:49:31 +03:00
kolo e1327c278c Update documentation and code snippets 2025-12-04 21:41:37 +03:00
kolo a57ea45c6f Update workflows 2025-12-04 21:04:59 +03:00
kolo baebeca55b Update documentation 2025-12-04 21:01:52 +03:00
kolo 6be0a94ba9 Update documentation and code snippets 2025-12-04 20:37:44 +03:00
kolo 087c76fed3 Update documentation and code snippets 2025-12-04 20:23:11 +03:00
kolo 723ed2210f Update documentation and code snippets 2025-12-04 18:26:28 +03:00
kolo c6fd01b7bc Update documentation and code snippets 2025-12-04 18:25:39 +03:00
kolo 9179eb8468 Update documentation and code snippets 2025-12-04 18:17:36 +03:00
kolo be083bb64d Update documentation and code snippets 2025-12-04 17:50:24 +03:00
kolo eeb5a752ec Update documentation and code snippets 2025-12-04 17:47:20 +03:00
kolo 9d420af0c5 Update documentation and code snippets 2025-12-04 17:31:45 +03:00
kolo d4129e27e7 Update documentation and code snippets 2025-12-04 17:23:22 +03:00
kolo 974d35fb82 Update documentation and code snippets 2025-12-04 15:46:48 +03:00
kolo da1ee9269a Update documentation and code snippets 2025-12-04 14:54:57 +03:00
kolo dca320297e Update documentation and code snippets 2025-12-03 16:21:22 +03:00
kolo 08660f81d7 Update documentation 2025-12-03 15:53:39 +03:00
kolo 52139e5405 Update documentation and code snippets 2025-12-03 14:30:33 +03:00
kolo 2a0ec209c3 Update documentation 2025-12-03 13:58:44 +03:00
kolo cd3095f42e Update documentation 2025-12-03 13:51:49 +03:00
kolo 2800a7ffc2 Update documentation and code snippets 2025-12-02 22:59:58 +03:00
kolo e6645730f0 Update documentation and code snippets 2025-12-02 22:26:30 +03:00
kolo eae8cdbb58 Update documentation and code snippets 2025-12-02 11:31:24 +03:00
kolo 7c20bf296b Update documentation and code snippets 2025-12-02 11:22:31 +03:00
kolo 2ff47398ba Update documentation 2025-12-02 11:02:09 +03:00
kolo 19906c1b1b Update documentation and code snippets 2025-12-02 10:51:44 +03:00
kolo 2a96dfcabe docs 2025-11-29 11:51:59 +03:00
kolo 47fda23431 docs 2025-11-28 17:24:31 +03:00
kolo d3bf5e703d docs 2025-11-28 14:54:27 +03:00
kolo be178b10c7 refactor and optimize argspace 2025-11-28 14:31:51 +03:00
kolo 1eaf2b6333 update 2025-11-27 21:50:30 +03:00
kolo e5be7b5d99 docs 2025-11-21 21:09:29 +03:00
kolo 2e76f68d4a docs 2025-11-21 19:41:35 +03:00
kolo 8edd59c1b8 docs 2025-11-07 21:25:35 +03:00
kolo 16e7cc21fb docs 2025-11-07 17:00:34 +03:00
kolo 4da876b774 docs 2025-11-04 11:21:35 +03:00
kolo 7b85b0f08d docs 2025-11-03 19:16:21 +03:00
kolo 270e91f705 Update translations 2025-11-03 19:01:43 +03:00
kolo 767d742060 docs 2025-11-03 14:53:07 +03:00
kolo b37096d790 add testing doc 2025-11-03 14:23:24 +03:00
kolo ad8c3af532 docs 2025-11-03 14:17:12 +03:00
kolo 02b02793d0 docs 2025-11-03 12:18:56 +03:00
kolo f0a18e89c8 docs 2025-11-02 18:34:33 +03:00
kolo 6f90712a17 docs 2025-11-02 18:02:48 +03:00
kolo 239e582241 docs 2025-11-02 15:30:41 +03:00
kolo 4967ec3d7f docs 2025-11-02 14:21:03 +03:00
kolo 7e02694cb0 docs 2025-11-02 01:19:59 +03:00
kolo 9c58c10152 docs 2025-11-02 01:04:31 +03:00
kolo 64c984a704 docs 2025-11-02 01:04:00 +03:00
kolo 15f97eab61 docs 2025-11-02 00:36:06 +03:00
kolo df7313912c docs 2025-11-02 00:17:28 +03:00
kolo acddb1fbc6 docs 2025-11-01 12:00:23 +03:00
kolo 9b28ef17ff docs 2025-11-01 11:54:46 +03:00
kolo e4a5c6d398 docs 2025-11-01 11:38:48 +03:00
kolo 0598f6e7a5 update docs 2025-11-01 11:27:46 +03:00
kolo c3395a3922 pretty gitignore 2025-11-01 10:25:56 +03:00
kolo b0924a9e89 dcos 2025-10-31 22:59:19 +03:00
kolo 1f15b4c093 docs 2025-10-28 10:34:34 +03:00
kolo bc6cb583a7 docs 2025-10-28 09:38:07 +03:00
kolo 7b40fff4c5 docs 2025-10-27 11:59:03 +03:00
kolo fc8597504f docs 2025-10-23 22:41:29 +03:00
kolo 667aeccc38 fix ci 2025-10-22 14:46:50 +03:00
kolo c88c1ac1cc fix ci 2025-10-22 14:01:27 +03:00
kolo ec42e63d80 fix ci and pyproject 2025-10-22 13:58:49 +03:00
kolo 4f5481fa70 tests 2025-10-22 13:40:26 +03:00
kolo f38da15bdb docs and fix 2025-10-21 22:56:29 +03:00
kolo 90e80d3454 docs 2025-10-21 12:19:05 +03:00
kolo 8a8a1739e7 docs 2025-10-21 11:54:36 +03:00
kolo bf6fe0f9ee docs 2025-10-21 10:33:46 +03:00
kolo 9ac24926af docs 2025-10-20 16:38:17 +03:00
kolo 6fa431a27a docs 2025-10-19 23:28:24 +03:00
kolo 87d232e835 docs 2025-10-19 23:26:06 +03:00
kolo 1314b11827 docs 2025-10-19 22:44:49 +03:00
kolo d146fc6b6b docs 2025-10-19 22:31:02 +03:00
kolo 9ff00ee785 docs 2025-10-19 21:10:07 +03:00
kolo 5d54375f14 docs 2025-10-19 20:34:23 +03:00
kolo 02bc775148 docs 2025-10-19 17:07:25 +03:00
kolo af73b9067d docs 2025-10-19 16:24:35 +03:00
kolo 67ac8c53ca docs 2025-10-19 16:21:44 +03:00
kolo dcbe03d08a docs 2025-10-19 16:20:16 +03:00
kolo 5b5668593c fix metadata 2025-10-19 15:12:18 +03:00
kolo f52932dc2e new metadata 2025-10-19 02:08:19 +03:00
kolo b6e5c57b8a docs 2025-10-19 01:13:14 +03:00
kolo 8324d3e5a2 docs 2025-10-18 02:04:06 +03:00
kolo a5af8337f3 Merge branch 'main' into docs/create_docs 2025-10-17 22:06:03 +03:00
kolo a2ac6a608f fix 2025-10-17 21:56:56 +03:00
kolo e4bff84c38 Merge pull request #5 from koloideal/feat/user_data_in_response
feat: bridge data between handlers, new tests for Response
2025-10-17 21:52:38 +03:00
kolo cd58f2a5d3 feat: bridge data between handlers, new tests for Response 2025-10-17 21:51:19 +03:00
kolo 182467502d fix: removing core documentation files, as there are separate pull requests for that 2025-10-17 21:11:12 +03:00
kolo db04a5bba7 docs: add new page in docs - Flags 2025-10-17 21:03:37 +03:00
kolo d37688392a docs: fix all references 2025-10-17 19:23:31 +03:00
kolo a25ede147f docs: continue making docs 2025-10-17 01:09:06 +03:00
kolo c87a0ce547 continue making docs 2025-10-16 18:51:07 +03:00
kolo 4e30aae916 fix ci 2025-10-16 17:03:18 +03:00
kolo 051b6f8b50 fix ci 2025-10-16 15:53:59 +03:00
kolo 5c14f9b8bc fix ci 2025-10-16 15:41:08 +03:00
kolo c38d539e33 fix ci 2025-10-16 09:43:51 +03:00
kolo cbf9c11a63 fix ci 2025-10-16 09:33:46 +03:00
kolo dfec91c509 fix ci 2025-10-16 09:14:47 +03:00
kolo eb18730789 fix ci 2025-10-15 19:25:04 +03:00
kolo 19716bdb5e fix ci 2025-10-15 19:23:01 +03:00
kolo 02687f5acd add new lang 2025-10-15 19:16:34 +03:00
kolo 7f5234b67a docs: create index.rst 2025-10-15 16:37:53 +03:00
kolo a4543cee92 docs: update translations 2025-10-15 16:23:52 +03:00
kolo 074ace7d54 fix ci 2025-10-15 16:17:52 +03:00
kolo e68a6c48ac fix ci 2025-10-15 15:31:45 +03:00
kolo fba2021114 fix ci 2025-10-15 15:30:53 +03:00
kolo 51d54ee9c8 fix ci 2025-10-15 15:25:31 +03:00
kolo 6359479b5b fix ci 2025-10-15 15:24:12 +03:00
kolo 4db3573292 fix conf 2025-10-15 15:23:17 +03:00
kolo 0cb41628dd fix img 2025-10-15 14:53:36 +03:00
kolo 648f9da2c6 fix img 2025-10-15 14:52:28 +03:00
kolo 79e768f943 img 2025-10-15 14:51:36 +03:00
kolo 2275ec1d00 docs 2025-10-15 14:47:36 +03:00
kolo bd37fab1ce continue create docs 2025-10-15 14:26:33 +03:00
kolo 6e3da8b4e4 docs: create index.rst 2025-10-15 13:40:19 +03:00
kolo f696805bc6 continue create docs 2025-10-15 11:22:20 +03:00
kolo 0fca3af35d start create docs 2025-10-14 22:34:19 +03:00
kolo a5e72161ef imports sort 2025-10-13 14:28:11 +03:00
kolo 36b4d16610 fix public api 2025-10-13 14:23:47 +03:00
kolo 4a895df52c feat: start make docs 2025-10-13 02:03:49 +03:00
kolo 35707e078e Merge pull request #3 from koloideal/feat/di
Implementation of the Dependency Injection principle
2025-10-13 01:55:28 +03:00
kolo 078cbdc4a8 fix typechecker error, chage version in readme and etc 2025-10-13 01:51:38 +03:00
kolo 462a8088e9 impl di in handlers with support custom provider 2025-10-13 01:13:28 +03:00
kolo e2753ef904 initial commit in new pr 2025-10-12 00:14:42 +03:00
kolo 8dfb95ec91 fix: docs config 2025-10-11 20:15:19 +03:00
kolo e67d08970f fix: docs config 2025-10-11 20:03:36 +03:00
kolo 1e5b220a22 feat/dcos 2025-10-11 19:59:50 +03:00
kolo 6f4f8c407a Merge pull request #2 from koloideal/feat/full_support_argparser
Added support for optional parameters for various types of arguments
2025-10-11 19:39:36 +03:00
kolo 2b76bea318 fix: tests, imports 2025-10-11 19:37:23 +03:00
kolo e481ee8775 extend arguments 2025-10-11 18:29:11 +03:00
kolo 0a1d462090 step by step 2025-10-09 22:01:06 +03:00
kolo b3b5e2e8a8 extend arguments 2025-10-09 20:23:52 +03:00
kolo 0bdc3f07c2 initial commit in new pr 2025-10-08 21:51:10 +03:00
kolo 77416cf22c release 1.1.1 2025-10-08 20:49:29 +03:00
kolo b6c84f1a91 fix ci 2025-10-08 13:44:12 +03:00
kolo c2d235e576 fix ci 2025-10-08 13:42:01 +03:00
kolo f7f5db58aa fix ci 2025-10-08 13:40:20 +03:00
kolo 73303b1c08 ref: typehints, enum instead of raw string, abc and other (#1)
Full code coverage with annotations, fixing errors in various linters: ruff, wps, etc. Fixing errors in type checkers: ty, mypy, pyright. Formatting and bringing code to a consistent style, applying best practices in various aspects.
2025-10-08 13:37:31 +03:00
kolo 22f1171192 fix: link to img in readme 2025-08-12 12:26:37 +03:00
kolo a844095fdc Update README.de.md 2025-08-06 14:57:31 +03:00
kolo a7c6a14705 Update README.md 2025-08-06 14:56:24 +03:00
kolo cfdb37330e Update README.ru.md 2025-08-06 14:52:33 +03:00
kolo aef6a9ca38 update .gitignore 2025-07-04 18:31:52 +03:00
kolo c8e0729be8 fix bugs 2025-05-27 14:19:54 +03:00
kolo c2bbc5f15d first steps sor adding metrics tests 2025-05-24 00:39:39 +03:00
kolo 0acbf54e44 first steps sor adding metrics tests 2025-05-23 22:12:12 +03:00
kolo c3d9541330 working 2025-05-23 14:33:13 +03:00
kolo f6561de9b3 wotk 2025-05-22 20:26:48 +03:00
kolo bebd84969b add Enum PossibleValues for bool values as values of possible_values argument in Flag 2025-05-22 12:10:32 +03:00
kolo 365347ea7f some fix 2025-05-21 17:01:35 +03:00
kolo 33cb528b1d some fix 2025-05-21 13:32:45 +03:00
kolo fd287c5da0 fix type hints with ty help, add ability to configure stdout capture when handling input by the router 2025-05-20 22:44:47 +03:00
kolo 45f410e3e8 make pre_cycle_setup faster on 4 sec, start implemtnation disable redirect stdout in router 2025-05-19 10:31:05 +03:00
kolo 8b06e9cd39 add metrics concept 2025-05-12 16:22:29 +03:00
kolo c38fe10006 translate readme on de 2025-05-10 22:07:52 +03:00
kolo 03cbc64f48 translate readme 2025-05-10 21:56:34 +03:00
kolo cbf7d3c578 new warning when triggers or aliases overlap 2025-05-10 21:31:59 +03:00
kolo ea2d068022 change dataclass to enum 2025-05-10 20:13:42 +03:00
kolo 5991851207 Update README.md 2025-05-10 00:29:03 +03:00
kolo f628c3b5b5 v1.0.2 2025-05-10 00:11:57 +03:00
kolo 05379712f4 some fix 2025-05-09 23:33:54 +03:00
kolo ed1cbf0fcf stable version 2025-05-09 23:27:34 +03:00
kolo 471f05369b Merge branch 'dev' of https://github.com/koloideal/Argenta into dev 2025-05-09 23:25:47 +03:00
kolo 13f7e33db1 ruff format 2025-05-09 23:25:21 +03:00
kolo 9a78aa9263 ruff check 2025-05-09 23:24:12 +03:00
kolo 58ccd6b26d Update README.md 2025-05-09 23:20:45 +03:00
kolo 73144f7ba4 Update README.md 2025-05-09 23:19:07 +03:00
kolo 650f4c9036 some fix 2025-05-09 18:23:08 +03:00
kolo 393f5c7d81 fix 2025-05-08 23:43:08 +03:00
kolo 9eb2bb6c46 new imgs 2025-05-08 23:37:42 +03:00
kolo 79b275eac7 some fix 2025-05-08 01:08:11 +03:00
kolo 07ac2af71e some fix 2025-05-07 19:14:19 +03:00
kolo c4b3aa7db8 working 2025-05-07 02:15:42 +03:00
kolo 61ef6a6466 all tests passed 2025-05-06 21:53:53 +03:00
kolo 477f3a7dec starting refactor tests 2025-05-04 16:40:10 +03:00
kolo adf3431388 more beautiful typehints warning 2025-05-04 03:08:54 +03:00
kolo 83955aa046 first beta - adding hints for similar commands, now - feature freezing 2025-05-04 02:13:05 +03:00
kolo 5a17e916eb work on stable major version 2025-04-30 15:48:38 +03:00
kolo 1159dda16e work on Response model 2025-04-30 00:08:49 +03:00
kolo 315508a36e work on Response 2025-04-29 20:40:47 +03:00
kolo 9d6598c4e0 work on Response model 2025-04-29 00:07:32 +03:00
kolo eb43806da6 new model - Response 2025-04-28 02:21:34 +03:00
kolo e076dbf84f new img in docs 2025-04-27 23:43:14 +03:00
kolo 2f090b6b47 new img in docs 2025-04-27 23:42:56 +03:00
kolo c9dbf2bbae fix print framed text with static dividing line 2025-04-27 23:27:08 +03:00
kolo e768c1bd2c fix 2025-04-27 21:29:14 +03:00
kolo 408450ec12 fix 2025-04-27 21:20:44 +03:00
kolo 106ca058be new tests 2025-04-27 14:11:01 +03:00
kolo b5ddfb3b35 new tests 2025-04-27 13:28:11 +03:00
kolo 61e4502e41 work, fix etc. 2025-04-26 22:23:35 +03:00
kolo 9b2fc87e33 release v1.0.0a1 2025-04-25 02:29:44 +03:00
kolo 89f09c42f8 pre-release v1.0.0 2025-04-24 21:26:41 +03:00
kolo 5bcae8fe68 Update README.md 2025-04-24 17:40:53 +03:00
kolo ca58008431 fix 2025-04-23 22:07:57 +03:00
kolo 30974f48eb fix 2025-04-23 22:06:10 +03:00
kolo df4ba080b0 new docs 2025-04-23 22:02:02 +03:00
kolo f93930d712 work 2025-04-23 21:45:04 +03:00
kolo 036c17ec9a last steps work on new docs, full complete write docstring for all objects 2025-04-23 20:54:03 +03:00
kolo 7281fdeabf new app preview img in docs 2025-04-23 20:50:31 +03:00
kolo 051ec6df28 steps 2025-04-20 23:58:15 +03:00
kolo 00a1e11fc1 new docs 2025-04-19 12:13:29 +03:00
kolo 584df9ba69 new docs 2025-04-18 12:50:30 +03:00
kolo a649022f1d work on docstrings 2025-04-15 23:49:51 +03:00
kolo 26a9d8a6da work on 2025-04-15 22:26:20 +03:00
kolo 9522b0161a work on 2025-04-15 01:09:03 +03:00
kolo e189f8d9aa big step 2025-04-14 16:38:53 +03:00
kolo 3ef8707cfa big step 2025-04-14 14:54:17 +03:00
kolo a5fdcab862 work on 2025-04-14 01:03:24 +03:00
kolo ba035881ee work on support args 2025-04-13 19:24:03 +03:00
kolo 34ebe55531 first steps 2025-04-13 14:39:53 +03:00
kolo 01c9d2dc6d first step 2025-04-13 14:12:08 +03:00
281 changed files with 22450 additions and 2076 deletions
+31
View File
@@ -0,0 +1,31 @@
name: mypy
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --group typecheckers
- name: Run type checker
run: uv run python -m mypy -p argenta
+5 -4
View File
@@ -2,9 +2,9 @@ name: ruff
on: on:
push: push:
branches: [ "kolo" ] branches: [ "main" ]
pull_request: pull_request:
branches: [ "kolo" ] branches: [ "main" ]
permissions: permissions:
contents: read contents: read
@@ -24,7 +24,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install ruff pip install uv
uv sync --group linters
- name: Run linter - name: Run linter
run: ruff check ./argenta run: uv run python -m ruff check ./src
+9 -6
View File
@@ -2,9 +2,9 @@ name: tests
on: on:
push: push:
branches: [ "kolo" ] branches: [ "main" ]
pull_request: pull_request:
branches: [ "kolo" ] branches: [ "main" ]
permissions: permissions:
contents: read contents: read
@@ -13,19 +13,22 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13", "3.14"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v3 - uses: actions/setup-python@v3
with: with:
python-version: "3.13" python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install poetry pip install uv
poetry install uv sync --group tests
- name: Run tests - name: Run tests
run: poetry run python -m unittest discover run: uv run python -m pytest tests
+327 -6
View File
@@ -1,7 +1,328 @@
.venv #### joe made this: http://goel.io/joe
.idea
dist
poetry.lock
*__pycache__/
*.hist*
metrics/reports/diagrams
*.dist
*build
*.exe
#### python ####
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
#### joe made this: http://goel.io/joe
#### visualstudiocode ####
.vscode/
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
!*.code-workspace
# Built Visual Studio Code Extensions
*.vsix
#### joe made this: http://goel.io/joe
#### jetbrains ####
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based HTTP Client
.idea/httpRequests
http-client.private.env.json
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Apifox Helper cache
.idea/.cache/.Apifox_Helper
.idea/ApifoxUploaderProjectSetting.xml
.zed
test.py
+17
View File
@@ -0,0 +1,17 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.12"
jobs:
post_install:
- sphinx-intl build --locale-dir docs/locales
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt
+20
View File
@@ -0,0 +1,20 @@
<a id='changelog-1.2.0'></a>
## 1.2.0 — 2026-02-07
### Added
- 100% coverage of the code base with tests
- 100% coverage with typhints
- 100% coverage of public API documentation in two languages - Russian and English
- cli attributes: highlighting valid commands, redesigned input history with auto-completion, interactive autocomplete selection menu for multiple candidates
- a metrics module that allows you to test the performance of various library units
- implementing a dependency injection pattern through an ioc container
- implementation of a context object for transferring data between handlers within a session
- adding a changelog
### Changed
- increased performance by several times (there will be real numbers in the next releases)
- reworking the internal API, highlighting different layers and reducing connectivity
- reworking the README and adding a translation for it
+66
View File
@@ -0,0 +1,66 @@
# Code of Conduct - Argenta
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, level of experience, education,
socio-economic status, nationality, personal appearance, race and religion.
## Our Standards
Examples of behaviour that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologising to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behaviour include:
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of
acceptable behaviour and will take appropriate and fair corrective action in
response to any instances of unacceptable behaviour.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban
temporarily or permanently any contributor for other behaviours that they deem
inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
reported to the community leaders responsible for enforcement at .
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version
[1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and
[2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md)
+264
View File
@@ -0,0 +1,264 @@
<!-- omit in toc -->
# Contributing to Argenta
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
<!-- omit in toc -->
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Styleguides](#styleguides)
- [Commit Messages](#commit-messages)
- [Join The Project Team](#join-the-project-team)
## Code of Conduct
This project and everyone participating in it is governed by the
[Argenta Code of Conduct](https://github.com/koloideal/Argenta/blob/main/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to .
-----
## I Have a Question
> If you want to ask a question, we assume that you have read the available [Documentation](https://argenta.readthedocs.io).
Before you ask a question, it is best to search for existing [Issues](https://github.com/koloideal/Argenta/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](https://github.com/koloideal/Argenta/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (cpython, pip, etc), depending on what seems relevant.
We will then take care of the issue as soon as possible.
<!--
You might want to create a separate issue tag for questions and include it in this description. People should then tag their issues accordingly.
Depending on how large the project is, you may want to outsource the questioning, e.g. to Stack Overflow or Gitter. You may add additional contact and information possibilities:
- IRC
- Slack
- Gitter
- Stack Overflow tag
- Blog
- FAQ
- Roadmap
- E-Mail List
- Forum
-->
-----
## I Want To Contribute
> ### Legal Notice <!-- omit in toc -->
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence.
### Reporting Bugs
<!-- omit in toc -->
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://argenta.readthedocs.io). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/koloideal/Argenta/issues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
<!-- omit in toc -->
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](https://github.com/koloideal/Argenta/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
<!-- You might want to create an issue template for bugs and errors that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
-----
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Argenta, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
<!-- omit in toc -->
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation](https://argenta.readthedocs.io) carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](https://github.com/koloideal/Argenta/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
<!-- omit in toc -->
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://github.com/koloideal/Argenta/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. <!-- this should only be included if the project has a GUI -->
- **Explain why this enhancement would be useful** to most Argenta users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
<!-- You might want to create an issue template for enhancement suggestions that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
-----
### Your First Code Contribution
Unsure where to begin contributing to Argenta? You can start by looking through `good first issue` and `help wanted` issues on our GitHub repository. These are issues that are well-suited for new contributors.
To get started with your first code contribution, please follow these steps to set up your local development environment.
1. Fork the `Argenta` repository on GitHub.
2. Clone your forked repository to your local machine:
```bash
git clone https://github.com/<YOUR_USERNAME>/Argenta.git
cd Argenta
```
3. Create and activate a Python virtual environment.
```bash
# For macOS/Linux
python3 -m venv .venv
source .venv/bin/activate
# For Windows
python -m venv .venv
.venv\Scripts\activate
```
4. Install the project dependencies, including the development tools.
```bash
pip install -e .[dev]
```
5. Create a new branch for your feature or bug fix. Use a descriptive name, like `fix/login-bug` or `feat/new-widget`.
```bash
git checkout -b your-new-branch-name
```
6. Make your changes! Write your code, and don't forget to add or update tests for your changes.
7. Run the test suite to ensure everything is working correctly.
```bash
python -m pytest tests
```
8. Commit your changes following our commit message styleguide and push them to your fork.
```bash
git add .
git commit -m "feat(widget): add the new super widget"
git push origin your-new-branch-name
```
9. Open a Pull Request from your forked branch to the `main` branch of the official Argenta repository. Provide a clear description of the problem and your solution. Include the relevant issue number if applicable.
-----
### Improving The Documentation
Good documentation is crucial for any project. We use Sphinx to generate our documentation from source files located in the `docs/` directory. We welcome any improvements, from fixing a simple typo to writing a whole new section.
We support documentation in two languages: Russian and English
To improve the documentation, you can follow a similar workflow as for code contributions:
1. Ensure your development environment is set up as described in the "Your First Code Contribution" section.
2. Navigate to the documentation directory.
```bash
cd docs
```
3. Make the necessary changes to the **Russian** version of the documentation - ``docs/index.rst`` and ``docs/root/*``
4. To build the documentation locally and see your changes, run:
```bash
make live-ru
```
5. Open `127.0.0.1:8000` in your web browser to preview the generated documentation.
6. Make your desired changes to the `.rst` or `.md` files in the `docs/source` directory.
7. After completing the work on the Russian documentation, it is necessary to create an English translation:
```bash
make update-langs
```
8. After updating the translation template, update the necessary translation files located at ``docs/locales/en/LC_MESSAGES``
8. Once you are happy with your changes, commit them and open a Pull Request. Use the `docs:` prefix in your commit message.
---
## Styleguides
### Commit Messages
We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for our commit messages. This leads to more readable messages that are easy to follow when looking through the project history and allows for automated changelog generation.
Each commit message consists of a **header**, a **body**, and a **footer**.
```
<type>(<scope>): <subject>
[optional body]
[optional footer]
```
The `<type>` must be one of the following:
* **feat**: A new feature for the user.
* **fix**: A bug fix for the user.
* **docs**: Documentation only changes.
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc).
* **refactor**: A code change that neither fixes a bug nor adds a feature.
* **perf**: A code change that improves performance.
* **test**: Adding missing tests or correcting existing tests.
* **chore**: Changes to the build process or auxiliary tools and libraries.
#### Examples
A simple fix:
``fix: correct typo in user authentication flow``
A new feature with a scope:
``feat(api): add new endpoint for user profiles``
---
## Join The Project Team
We are always looking for enthusiastic and dedicated people to join our project team. If you are a regular contributor and have shown a deep understanding of the project's goals and architecture, you might be a good candidate to become a maintainer.
Active members of the community can become team members. This typically involves:
* Consistently contributing high-quality code and documentation.
* Helping other users by answering questions and triaging issues.
* Reviewing Pull Requests from other contributors with constructive feedback.
If you are interested in becoming a more permanent part of the team, the best way to start is by being an active and helpful member of the community. The existing maintainers will notice your efforts and may reach out with an invitation to join the team.
+70 -548
View File
@@ -1,583 +1,105 @@
# Argenta ![preview](https://i.ibb.co/whkMfHw5/a-minimalist-logo-design-featuring-the-t-t-GXhfz-NFRwi-k-ROq-NMd-LWA-R7-6ru-YSh-G0kz-LKr5t-ZQ.jpg)
**Argenta** is a simple and elegant framework for building modular CLI applications. It provides a clean and intuitive way to create context-aware command-line tools with isolated command scopes.
Argenta is the **"Simplest"**, **"Most Modular"**, and **"Most Elegant"** way to build interactive CLI applications in Python.
📖 **Read the full documentation:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)<br>
🌍 **Other languages:** [RU](https://github.com/koloideal/Argenta/blob/main/README.ru.md)
--- ---
## Описание ![preview](https://vhs.charm.sh/vhs-2hvLCEgclmwZPJZt1vLGKi.gif)
**Argenta** — Python library for creating TUI
![preview](https://github.com/koloideal/Argenta/blob/kolo/imgs/mock_app_preview_last.png?raw=True) **Argenta** allows you to build interactive CLI applications incredibly easily. There's no need to manually parse complex command structures or manage state transitions — just use routers and commands!
Пример внешнего вида TUI, написанного с помощью Argenta
--- ## ✨ Installing Argenta
# Установка Argenta is available on ``PyPI``:
```bash
pip install argenta ```console
``` $ python -m pip install argenta
or
```bash
poetry add argenta
``` ```
--- or using ``uv``:
# Быстрый старт ```console
$ uv add argenta
```
Argenta officially supports Python 3.12+.
## 🚀 Supported Features & Best Practices
Argenta is ready for the demands of building scalable, robust and maintainable CLI applications.
- **Interactive Sessions**: Unlike traditional CLI tools, ``Argenta`` creates cyclic sessions, allowing users to execute commands sequentially without restarting the application.
- **Declarative Syntax**: Commands and their handlers are declared using simple decorators, making the code intuitive and allowing you to focus on "what" you want to do, not "how".
- **Native DI**: Thanks to integration with [dishka](https://dishka.readthedocs.io/en/stable/), you can easily inject dependencies directly into command handlers, simplifying testing, avoiding mutable globals, and much more.
- **Automatic Validation and Parsing**: The library handles command-line flags and arguments processing, including parsing, validation, and type conversion.
- **Flexible Configuration**: You can easily customize system messages, output formatting, create custom handlers for non-standard behavior, and more.
Need something more? Create an **issue**, we're open to suggestions.
## 📝 Why did we create this?
Building complex CLI applications often requires managing different contexts and command scopes. For example, when creating a utility similar to the Metasploit Framework, users need to enter specific scopes (like selecting a scanning module) and then access commands specific only to that context.
Traditional CLI frameworks don't provide an elegant way to handle this kind of modular, context-aware architecture. Argenta was built to solve this problem by providing a simple and concise way to encapsulate CLI functionality in isolated, abstracted environments.
We believe that building CLI applications should be as pleasant as building web applications with modern frameworks. Argenta brings the router pattern and clean separation of concerns to the CLI world.
## 🚀 Quick Start
Here's a simple example to get you started:
Пример простейшей оболочки с командой без зарегистрированных флагов
```python ```python
# routers.py # routers.py
from argenta.router import Router from argenta import Router, Response
from argenta.command import Command from argenta.command import Flag, Command
router = Router() router = Router()
@router.command(Command("hello")) @router.command(Command("hello"))
def handler(): def handler(response: Response):
print("Hello, world!") """A simple hello world command"""
print("Hello, world!")
@router.command(Command("greet", flags=Flag('name')))
def greet_handler(response: Response):
"""Greet a user by name"""
name_flag = response.input_flags.get_flag_by_name('name')
if name_flag:
print(f"Hello, {name_flag.input_value}!")
else:
print("Hello, Stranger!")
``` ```
```python ```python
# main.py # main.py
from argenta.app import App from argenta import App, Orchestrator
from routers import router from .routers import router
app: App = App() app = App()
orchestrator = Orchestrator()
def main() -> None: def main() -> None:
# Include your routers
app.include_router(router) app.include_router(router)
app.start_polling()
# Start the interactive CLI
orchestrator.start_polling(app)
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
Пример оболочки с командой, у которой зарегистрированы флаги
```python That's it! You now have a fully functional interactive CLI application.
# routers.py
import re
from argenta.router import Router
from argenta.command import Command
from argenta.command.flag import Flags, Flag, InputFlags
router = Router() ## 📚 Documentation
registered_flags = Flags( Full documentation is available at [argenta.readthedocs.io](https://argenta.readthedocs.io/)
Flag(name='host',
prefix='--',
possible_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')),
Flag('port', '--', re.compile(r'^[0-9]{1,4}$')))
@router.command(Command("hello"))
def handler():
print("Hello, world!")
@router.command(Command(trigger="ssh",
description='connect via ssh',
flags=registered_flags))
def handler_with_flags(flags: InputFlags):
for flag in flags:
print(f'Flag name: {flag.get_name()}\n'
f'Flag value: {flag.get_value()}')
```
--- ---
# *classes* : MIT 2025 kolo | made by [kolo](https://t.me/kolo_id)
---
## *class* : : `App`
Класс, определяющий поведение и состояние оболочки
### Конструктор
```python
App(prompt: str = '[italic dim bold]What do you want to do?\n',
initial_message: str = '\nArgenta\n',
farewell_message: str = '\nSee you\n',
exit_command: Command = Command('Q', 'Exit command'),
system_points_title: str | None = 'System points:',
ignore_command_register: bool = True,
dividing_line: StaticDividingLine | DynamicDividingLine = StaticDividingLine(),
repeat_command_groups: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = AutoCompleter(),
print_func: Callable[[str], None] = Console().print)
```
**Аргументы:**
- **name : mean**
- `prompt` (`str`): Сообщение перед вводом команды.
- `initial_message` (`str`): Приветственное сообщение при запуске.
- `farewell_message` (`str`): Сообщение при выходе.
- `exit_command` (`Command`): Сущность команды, которая будет отвечать за завершение работы.
- `system_points_title` (`str`): Заголовок перед списком системных команд.
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
- `dividing_line` (`StaticDividingLine | DynamicDividingLine`): Разделительная строка.
- `repeat_command_groups` (`bool`): Повторять описание команд перед вводом.
- `override_system_messages` (`bool`): Переопределить ли дефолтное оформление сообщений ([подробнее см.](#override_defaults))
- `autocompleter` (`AutoCompleter`): Сущность автодополнителя ввода.
- `print_func` (`Callable[[str], None]`): Функция вывода текста в терминал.
---
### ***methods***
---
#### **.start_polling() -> `None`**
*method mean* **::** Запускает цикл обработки ввода
---
#### **.include_router(router: Router) -> `None`**
*param* `router: Router` **::** Регистрируемый роутер
*required* **::** True
*method mean* **::** Регистрирует роутер в оболочке
---
#### **.include_routers(\*routers: Router) -> `None`**
*param* `routers: Router` **::** Неограниченное количество регистрируемых роутеров
*required* **::** True
*method mean* **::** Регистрирует роутер в оболочке
---
#### **.set_description_message_pattern(pattern: str) -> `None`**
*param* `pattern: str` **::** Паттерн описания команды при её выводе в консоль
*required* **::** True
*example* **::** `"[{command}] *=*=* {description}"`
*method mean* **::** Устанавливает паттерн описания команд, который будет использован
при выводе в консоль
---
#### **.add_message_on_startup(message: str) -> `None`**
*param* `message: str` **::** Сообщение, которое будет выведено при запуске приложения
*required* **::** True
*example* **::** `Message on startup`
*method mean* **::** Устанавливает паттерн описания команд, который будет использован
при выводе в консоль
---
<a name="custom_handler"></a>
#### **.repeated_input_flags_handler: `Callable[[str], None])`**
*example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером повторяющихся флагов
---
#### **.invalid_input_flags_handler: `Callable[[str], None])`**
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером команды с некорректным синтаксисом флагов
---
#### **.unknown_command_handler: `Callable[[str], None]`**
*example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером неизвестной команды
---
#### **.empty_command_handler: `Callable[[str], None])`**
*example* **::** `lambda: print_func(f'Empty input command')`
*attr mean* **::** Устанавливает функцию, которой будет передано управление при
вводе юзером пустой команды
---
### Примечания
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
- Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
у __<u>разных</u>__ роутеров
- Наиболее частые сообщение при запуске предопределены и доступны для быстрого
использования: `argenta.app.defaults.PredeterminedMessages`
<a name="override_defaults"></a>
- Если `override_system_messages`=`False`, то при переопределении таких атрибутов как `initial_message` и
`farawell_message` будет использовано дефолтное оформление текста, в виде красного ascii арта, при значении
`override_system_messages`=`True` системные сообщения будут отображены в точности какими были переданы
### Исключения
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
- `IncorrectNumberOfHandlerArgsException` — У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент).
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
---
## *class* :: `AutoCompleter`
Класс, экземпляр которого представляет собой автодополнитель ввода
### Конструктор
```python
AutoCompleter(history_filename: str = False,
autocomplete_button: str = 'tab')
```
**Аргументы:**
- **name : mean**
- `history_filename` (`str` | `False`): Путь к файлу, который будет являться или является
историй пользовательского ввода, в последующем эти команды будут автодополняться, файл
может не существовать при инициализации, тогда он будет создан, при значении аргумента `False`
история пользовательского ввода будет существовать только в пределах сессии и не сохраняться в файл
- `autocomplete_button` (`str`): Строковое обозначение кнопки на клавиатуре, которая будет
использоваться для автодополнения при вводе, по умолчанию `tab`
---
## *class* :: `StaticDivideLine`
Класс, экземпляр которого представляет собой строковый разделитель фиксированной длины
### Конструктор
```python
StaticDivideLine(unit_part: str = '-',
length: int = 25)
```
**Аргументы:**
- **name : mean**
- `unit_part` (`str`): Единичная часть строкового разделителя
- `length` (`int`): Длина строкового разделителя
---
## *class* :: `DinamicDivideLine`
Строковый разделитель динамической длины, которая определяется длиной обрамляемого вывода команды
### Конструктор
```python
DinamicDivideLine(unit_part: str = '-')
```
**Аргументы:**
- **name : mean**
- `unit_part` (`str`): Единичная часть строкового разделителя
---
## *class* :: `Router`
Класс, который определяет и конфигурирует обработчики команд
### Конструктор
```python
Router(title: str | None = None,
name: str = 'Default')
```
**Аргументы:**
- **name : mean**
- `title` (`str`): Заголовок группы команд.
- `name` (`str`): Персональное название роутера
---
### ***methods***
---
#### **command(command: Command)**
*param* `command: Command` **::** Экземпляр класса `Command`, который определяет строковый триггер команды,
допустимые флаги команды и другое
*required* **::** True
*example* **::** `Command(command='ssh', description='connect via ssh')`
*method mean* **::** Декоратор, который регистрирует функцию как обработчик команды
---
#### **.get_name() -> `str`**
*method mean* **::** Возвращает установленное название роутера
---
#### **.get_title() -> `str`**
*method mean* **::** Возвращает установленный заголовок группы команд данного роутера
---
### Исключения
- `RepeatedFlagNameException` - Повторяющиеся зарегистрированные флаги в команде
- `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды
- `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды)
- `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент)
- `TriggerCannotContainSpacesException` - У регистрируемой команды в триггере содержатся пробелы
---
## *class* :: `Command`
Класс, экземпляр которого определяет строковый триггер хэндлера и конфигурирует его атрибуты
### Конструктор
```python
Command(trigger: str,
description: str = None,
flags: Flag | Flags = None)
```
**Аргументы:**
- **name : mean**
- `trigger` (`str`): Строковый триггер
- `description` (`str`): Описание команды, которое будет выведено в консоль при запуске оболочки
- `flags` (`Flag | Flags`): Флаги, которые будут обработаны при их наличии во вводе юзера
---
#### **.get_trigger() -> `str`**
*method mean* **::** Возвращает строковый триггер экземпляра
---
#### **.get_description() -> `str`**
*method mean* **::** Возвращает описание команды
---
#### **.get_registered_flags() -> `Flags | None`**
*method mean* **::** Возвращает зарегистрированные флаги экземпляра
---
### Исключения
- `UnprocessedInputFlagException` - Некорректный синтаксис ввода команды
- `RepeatedInputFlagsException` - Повторяющиеся флаги во введённой команде
- `EmptyInputCommandException` - Введённая команда является пустой(не содержит символов)
**Примечание**
Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса
`App`, а также по дефолту обрабатываются, при желании можно задать пользовательские
обработчики для этих исключений ([подробнее см.](#custom_handler))
---
## *class* :: `Flag`
Класс, экземпляры которого в большинстве случаев передаются при создании
экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды
### Конструктор
```python
Flag(name: str,
prefix: typing.Literal['-', '--', '---'] = '-',
possible_values: list[str] | typing.Pattern[str] | False = True)
```
---
**Аргументы:**
- **name : mean**
- `name` (`str`): Имя флага
- `prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
- `possible_values` (`list[str] | Pattern[str] | bool`): Множество допустимых значений флага, может быть задано
списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`), при значении
аргумента `False` у введённого флага не может быть значения, иначе будет вызвано исключение и обработано соответствующим
еррор-хэндлером
---
### ***methods***
---
#### **.get_string_entity() -> `str`**
*method mean* **::** Возвращает строковое представление флага(префикс + имя)
---
#### **.get_name() -> `str`**
*method mean* **::** Возвращает имя флага
---
#### **.get_prefix() -> `str`**
*method mean* **::** Возвращает префикс флага
---
## *class* :: `InputFlag`
Класс, экземпляры которого являются введёнными флагами команды, передаётся в хэндлер команды
через `InputFlags`
---
### Примечания
- Наиболее часто используемые флаги предопределены и доступны для быстрого использования:
`argenta.command.flag.defaults.PredeterminedFlags`
---
### Конструктор
```python
InputFlag(name: str,
prefix: typing.Literal['-', '--', '---'] = '-',
value: str = None)
```
---
**Аргументы:**
- **name : mean**
- `name` (`str`): Имя флага
- `prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
- `value` (`str`): Значение введённого флага, если оно есть
---
### ***methods***
---
#### **.get_value() -> `str | None`**
*method mean* **::** Возвращает значение введённого флага
---
## *class* :: `Flags`
Класс, объединяющий список флагов в один объект, используется в качестве
передаваемого аргумента `flags` экземпляру класса `Command`, при регистрации
хэндлера
### Конструктор
```python
Flags(*flags: Flag)
```
---
**Аргументы:**
- **name : mean**
- `*flags` (`Flag`): Неограниченное количество передаваемых флагов
---
### ***methods***
---
#### **.get_flags() -> `list[Flag]`**
*method mean* **::** Возвращает зарегистрированные флаги
---
#### **.add_flag(flag: Flag) -> `None`**
*method mean* **::** Добавляет флаг в группу
---
#### **.add_flags(flags: list[Flag]) -> `None`**
*method mean* **::** Добавляет флаги в группу
---
#### **.get_flag(name: str) -> `Flag | None`**
*param* `name: str` **::** Строковый триггер флага без префикса
*required* **::** True
*example* **::** `'host'`
*method mean* **::** Возвращает флаг по его триггеру или `None`, если флаг не найден
---
## *class* :: `InputFlags`
Класс, объединяющий список введённых флагов в один объект, передаётся соответствующему хэндлеру
в качестве аргумента
### Конструктор
```python
InputFlags(*flags: Flag)
```
---
**Аргументы:**
- **name : mean**
- `*flags` (`InputFlag`): Неограниченное количество передаваемых флагов
---
### ***methods***
---
#### **.get_flags() -> `list[Flag]`**
*method mean* **::** Возвращает введённые флаги
---
#### **.get_flag(name: str) -> `InputFlag | None`**
*param* `name: str` **::** Строковый триггер флага без префикса
*required* **::** True
*example* **::** `'host'`
*method mean* **::** Возвращает введённый флаг по его триггеру или `None`, если флаг не найден
---
# Тесты
Запуск тестов:
```bash
python -m unittest discover
```
or
```bash
python -m unittest discover -v
```
+108
View File
@@ -0,0 +1,108 @@
![preview](https://i.ibb.co/whkMfHw5/a-minimalist-logo-design-featuring-the-t-t-GXhfz-NFRwi-k-ROq-NMd-LWA-R7-6ru-YSh-G0kz-LKr5t-ZQ.jpg)
**Argenta** — это простой и элегантный фреймворк для создания модульных CLI-приложений. Он предоставляет чистый и интуитивный способ создания контекстно-зависимых инструментов командной строки с изолированными областями команд.
Argenta — это **"Самый простой"**, **"Самый модульный"** и **"Самый элегантный"** способ создания интерактивных CLI-приложений на Python.
📖 **Читайте полную документацию:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)<br>
🌍 **Другие языки:** [EN](https://github.com/koloideal/Argenta/blob/main/README.md)
---
![preview](https://vhs.charm.sh/vhs-2hvLCEgclmwZPJZt1vLGKi.gif)
**Argenta** позволяет создавать интерактивные CLI-приложения невероятно легко. Не нужно вручную парсить сложные структуры команд или управлять переходами состояний — просто используйте роутеры и команды!
## ✨ Установка Argenta
Argenta доступна на ``PyPI``:
```console
$ python -m pip install argenta
```
или с использованием ``uv``:
```console
$ uv add argenta
```
Argenta официально поддерживает Python 3.12+.
## 🚀 Поддерживаемые возможности и лучшие практики
Argenta готова к требованиям создания масштабируемых, надежных и поддерживаемых CLI-приложений.
- **Интерактивные сессии**: В отличие от традиционных CLI-инструментов, ``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение.
- **Декларативный синтаксис**: Команды и их обработчики объявляются с помощью простых декораторов, что делает код интуитивно понятным и позволяет сосредоточиться на том, "что" вы хотите сделать, а не "как".
- **Нативный DI**: Благодаря интеграции с [dishka](https://dishka.readthedocs.io/en/stable/), вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование, позволяет избежать мутабельных глобалов и многое другое.
- **Автоматическая валидация и парсинг**: Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов.
- **Гибкая настройка**: Вы можете легко кастомизировать системные сообщения, форматирование вывода, ссоздавать кастомные обработчики нестандартного поведения и т.д.
Нужно что-то еще? Создайте **issue**, мы открыты к предложениям.
## 📝 Зачем мы это создали?
Создание сложных CLI-приложений часто требует управления различными контекстами и областями команд. Например, при создании утилиты, подобной Metasploit Framework, пользователям нужно входить в определенные области (например, выбирать модуль сканирования), а затем получать доступ к командам, специфичным только для этого контекста.
Традиционные CLI-фреймворки не предоставляют элегантного способа обработки такой модульной, контекстно-зависимой архитектуры. Argenta была создана для решения этой проблемы, предоставляя простой и лаконичный способ инкапсуляции CLI-функциональности в изолированные, абстрагированные среды.
Мы считаем, что создание CLI-приложений должно быть таким же приятным, как создание веб-приложений с современными фреймворками. Argenta привносит паттерн роутеров и чистое разделение ответственности в мир CLI.
## 🚀 Быстрый старт
Вот простой пример для начала работы:
```python
# routers.py
from argenta import Router, Response
from argenta.command import Flag, Command
router = Router()
@router.command(Command("hello"))
def handler(response: Response):
"""Простая команда hello world"""
print("Привет, мир!")
@router.command(Command("greet", flags=Flag('name')))
def greet_handler(response: Response):
"""Поприветствовать пользователя по имени"""
name_flag = response.input_flags.get_flag_by_name('name')
if name_flag:
print(f"Привет, {name_flag.input_value}!")
else:
print("Привет, Незнакомец!")
```
```python
# main.py
from argenta import App, Orchestrator
from .routers import router
app = App()
orchestrator = Orchestrator()
def main() -> None:
# Подключите ваши роутеры
app.include_router(router)
# Запустите интерактивный CLI
orchestrator.start_polling(app)
if __name__ == '__main__':
main()
```
Вот и всё! Теперь у вас есть полностью функциональное интерактивное CLI-приложение.
## 📚 Документация
Полная документация доступна на [argenta.readthedocs.io](https://argenta.readthedocs.io/)
---
MIT 2025 kolo | made by [kolo](https://t.me/kolo_id)
+54
View File
@@ -0,0 +1,54 @@
# Security Policy
The Argenta team takes security seriously. We appreciate your efforts to responsibly disclose your findings, and we will make every effort to acknowledge your contributions.
## Supported Versions
This table shows the versions of Argenta that are currently supported with security updates.
| Version | Supported |
|---------|-----------------|
| 1.1.x | ✅ |
| < 1.1 | ❌ |
## Reporting a Vulnerability
If you believe you have found a security vulnerability in Argenta, please report it to us through one of the following methods:
- **Email**: Send a detailed report to `kolo.is.main@gmailcom`.
- **GitHub Security Advisories**: You can create a new security advisory directly in the Argenta repository.
**Please do not report security vulnerabilities through public GitHub issues.**
### What to Include
To help us understand and resolve the issue quickly, please include the following information in your report:
- A clear and descriptive title.
- The affected version(s) of Argenta.
- A detailed description of the vulnerability.
- Step-by-step instructions to reproduce the issue (a Proof-of-Concept).
- The potential impact of the vulnerability.
- Any suggested mitigations or fixes, if you have any.
You can expect a response from us within 48 hours to acknowledge receipt of your report.
## Disclosure Process
1. Upon receiving a vulnerability report, we will assign it to a team member and begin our investigation.
2. We will confirm the vulnerability and determine its severity.
3. We will work on a patch to address the issue.
4. Once the patch is ready, we will coordinate with you to schedule a release and a public disclosure. We prefer to disclose vulnerabilities through a GitHub Security Advisory.
5. We will credit you for your discovery in the advisory, unless you prefer to remain anonymous.
## Security Best Practices
As a user of Argenta, we recommend the following best practices to keep your application secure:
- Always use the latest version of Argenta.
- Regularly scan your project's dependencies for known vulnerabilities.
- If your application handles sensitive data, ensure it is stored and transmitted securely.
## Bug Bounty Program
Currently, we do not have a formal bug bounty program. However, we deeply appreciate the work of security researchers and may offer non-monetary recognition for significant contributions.
-3
View File
@@ -1,3 +0,0 @@
__all__ = ["App"]
from argenta.app.models import App
-4
View File
@@ -1,4 +0,0 @@
__all__ = ["AutoCompleter"]
from argenta.app.autocompleter.entity import AutoCompleter
-47
View File
@@ -1,47 +0,0 @@
import os
import readline
class AutoCompleter:
def __init__(self, history_filename: str = False, autocomplete_button: str = 'tab'):
self.history_filename = history_filename
self.autocomplete_button = autocomplete_button
self.matches = []
def complete(self, text, state):
matches = sorted(cmd for cmd in self.get_history_items() if cmd.startswith(text))
if len(matches) > 1:
common_prefix = matches[0]
for match in matches[1:]:
i = 0
while i < len(common_prefix) and i < len(match) and common_prefix[i] == match[i]:
i += 1
common_prefix = common_prefix[:i]
if state == 0:
readline.insert_text(common_prefix[len(text):])
readline.redisplay()
return None
elif len(matches) == 1:
return matches[0] if state == 0 else None
else:
return None
def initial_setup(self, all_commands: list[str]):
if self.history_filename:
if os.path.exists(self.history_filename):
readline.read_history_file(self.history_filename)
else:
for line in all_commands:
readline.add_history(line)
readline.set_completer(self.complete)
readline.set_completer_delims(readline.get_completer_delims().replace(' ', ''))
readline.parse_and_bind(f'{self.autocomplete_button}: complete')
def exit_setup(self):
if self.history_filename:
readline.write_history_file(self.history_filename)
@staticmethod
def get_history_items():
return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)]
-9
View File
@@ -1,9 +0,0 @@
from dataclasses import dataclass
@dataclass
class PredeterminedMessages:
USAGE = '[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]'
HELP = '[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]'
AUTOCOMPLETE = '[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>'
-4
View File
@@ -1,4 +0,0 @@
__all__ = ["StaticDividingLine", "DynamicDividingLine"]
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
-24
View File
@@ -1,24 +0,0 @@
class BaseDividingLine:
def __init__(self, unit_part: str = '-'):
self.unit_part = unit_part
def get_unit_part(self):
if len(self.unit_part) == 0:
return ' '
else:
return self.unit_part[0]
class StaticDividingLine(BaseDividingLine):
def __init__(self, unit_part: str = '-', length: int = 25):
super().__init__(unit_part)
self.length = length
def get_full_line(self):
return f'\n[dim]{self.length * self.get_unit_part()}[/dim]\n'
class DynamicDividingLine(BaseDividingLine):
def get_full_line(self, length: int):
return f'\n[dim]{self.get_unit_part() * length}[/dim]\n'
-10
View File
@@ -1,10 +0,0 @@
class NoRegisteredRoutersException(Exception):
def __str__(self):
return "No Registered Router Found"
class NoRegisteredHandlersException(Exception):
def __init__(self, router_name):
self.router_name = router_name
def __str__(self):
return f"No Registered Handlers Found For '{self.router_name}'"
-265
View File
@@ -1,265 +0,0 @@
from typing import Callable
from rich.console import Console
from rich.markup import escape
from art import text2art
from contextlib import redirect_stdout
import io
import re
from argenta.command.models import Command, InputCommand
from argenta.router import Router
from argenta.router.defaults import system_router
from argenta.app.autocompleter import AutoCompleter
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException,
BaseInputCommandException)
from argenta.app.exceptions import (NoRegisteredRoutersException,
NoRegisteredHandlersException)
from argenta.app.registered_routers.entity import RegisteredRouters
class AppInit:
def __init__(self,
prompt: str = '[italic dim bold]What do you want to do?\n',
initial_message: str = '\nArgenta\n',
farewell_message: str = '\nSee you\n',
exit_command: Command = Command('Q', 'Exit command'),
system_points_title: str | None = 'System points:',
ignore_command_register: bool = True,
dividing_line: StaticDividingLine | DynamicDividingLine = StaticDividingLine(),
repeat_command_groups: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = AutoCompleter(),
print_func: Callable[[str], None] = Console().print) -> None:
self._prompt = prompt
self._print_func = print_func
self._exit_command = exit_command
self._system_points_title = system_points_title
self._dividing_line = dividing_line
self._ignore_command_register = ignore_command_register
self._repeat_command_groups_description = repeat_command_groups
self._override_system_messages = override_system_messages
self._autocompleter = autocompleter
self._farewell_message = farewell_message
self._initial_message = initial_message
self._description_message_gen: Callable[[str, str], str] = lambda command, description: f'[bold red]{escape('['+command+']')}[/bold red] [blue dim]*=*=*[/blue dim] [bold yellow italic]{escape(description)}'
self._registered_routers: RegisteredRouters = RegisteredRouters()
self._messages_on_startup = []
self._invalid_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'[red bold]Incorrect flag syntax: {escape(raw_command)}')
self._repeated_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'[red bold]Repeated input flags: {escape(raw_command)}')
self._empty_input_command_handler: Callable[[], None] = lambda: print_func('[red bold]Empty input command')
self._unknown_command_handler: Callable[[InputCommand], None] = lambda command: print_func(f"[red bold]Unknown command: {escape(command.get_trigger())}")
self._exit_command_handler: Callable[[], None] = lambda: print_func(self._farewell_message)
class AppSetters(AppInit):
def set_description_message_pattern(self, pattern: Callable[[str, str], str]) -> None:
self._description_message_gen: Callable[[str, str], str] = pattern
def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
self._invalid_input_flags_handler = handler
def set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None:
self._repeated_input_flags_handler = handler
def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
self._unknown_command_handler = handler
def set_empty_command_handler(self, handler: Callable[[], None]) -> None:
self._empty_input_command_handler = handler
def set_exit_command_handler(self, handler: Callable[[], None]) -> None:
self._exit_command_handler = handler
class AppPrinters(AppInit):
def _print_command_group_description(self):
for registered_router in self._registered_routers:
if registered_router.get_title():
self._print_func(registered_router.get_title())
for command_handler in registered_router.get_command_handlers():
self._print_func(self._description_message_gen(
command_handler.get_handled_command().get_trigger(),
command_handler.get_handled_command().get_description()))
self._print_func('')
def _print_framed_text_with_dynamic_line(self, text: str):
clear_text = re.sub(r'\u001b\[[0-9;]*m', '', text)
max_length_line = max([len(line) for line in clear_text.split('\n')])
max_length_line = max_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10
self._print_func(self._dividing_line.get_full_line(max_length_line))
print(text.strip('\n'))
self._print_func(self._dividing_line.get_full_line(max_length_line))
def _print_framed_text(self, text: str):
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(self._dividing_line.get_full_line())
self._print_func(text)
self._print_func(self._dividing_line.get_full_line())
elif isinstance(self._dividing_line, DynamicDividingLine):
self._print_framed_text_with_dynamic_line(text)
class AppNonStandardHandlers(AppPrinters):
def _is_exit_command(self, command: InputCommand):
if command.get_trigger().lower() == self._exit_command.get_trigger().lower():
if self._ignore_command_register:
system_router.input_command_handler(command)
return True
elif command.get_trigger() == self._exit_command.get_trigger():
system_router.input_command_handler(command)
return True
return False
def _is_unknown_command(self, command: InputCommand):
for router_entity in self._registered_routers:
for command_handler in router_entity.get_command_handlers():
handled_command_trigger = command_handler.get_handled_command().get_trigger()
handled_command_aliases = command_handler.get_handled_command().get_aliases()
if handled_command_trigger.lower() == command.get_trigger().lower() and self._ignore_command_register:
return False
elif handled_command_trigger == command.get_trigger():
return False
elif handled_command_aliases:
if (command.get_trigger().lower() in [x.lower() for x in handled_command_aliases]) and self._ignore_command_register:
return False
elif command.get_trigger() in handled_command_trigger:
return False
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(self._dividing_line.get_full_line())
self._unknown_command_handler(command)
self._print_func(self._dividing_line.get_full_line())
elif isinstance(self._dividing_line, DynamicDividingLine):
with redirect_stdout(io.StringIO()) as f:
self._unknown_command_handler(command)
res: str = f.getvalue()
self._print_framed_text_with_dynamic_line(res)
return True
def _error_handler(self, error: BaseInputCommandException, raw_command: str) -> None:
match error:
case UnprocessedInputFlagException():
self._invalid_input_flags_handler(raw_command)
case RepeatedInputFlagsException():
self._repeated_input_flags_handler(raw_command)
case EmptyInputCommandException():
self._empty_input_command_handler()
class AppValidators(AppInit):
def _validate_number_of_routers(self) -> None:
if not self._registered_routers:
raise NoRegisteredRoutersException()
def _validate_included_routers(self) -> None:
for router in self._registered_routers:
if not router.get_command_handlers():
raise NoRegisteredHandlersException(router.get_name())
class AppSetups(AppValidators, AppPrinters):
def _setup_system_router(self):
system_router.set_title(self._system_points_title)
@system_router.command(self._exit_command)
def exit_command():
self._exit_command_handler()
if system_router not in self._registered_routers.get_registered_routers():
system_router.set_ignore_command_register(self._ignore_command_register)
self._registered_routers.add_registered_router(system_router)
def _setup_default_view(self):
if not self._override_system_messages:
self._initial_message = f'\n[bold red]{text2art(self._initial_message, font='tarty1')}\n\n'
self._farewell_message = (
f'[bold red]\n{text2art(f'\n{self._farewell_message}\n', font='chanky')}[/bold red]\n'
f'[red i]github.com/koloideal/Argenta[/red i] | [red bold i]made by kolo[/red bold i]\n')
def _pre_cycle_setup(self):
self._setup_default_view()
self._setup_system_router()
self._validate_number_of_routers()
self._validate_included_routers()
all_triggers: list[str] = []
for router_entity in self._registered_routers:
all_triggers.extend(router_entity.get_triggers())
all_triggers.extend(router_entity.get_aliases())
self._autocompleter.initial_setup(all_triggers)
self._print_func(self._initial_message)
for message in self._messages_on_startup:
self._print_func(message)
print('\n\n')
if not self._repeat_command_groups_description:
self._print_command_group_description()
class App(AppSetters, AppNonStandardHandlers, AppSetups):
def start_polling(self) -> None:
self._pre_cycle_setup()
while True:
if self._repeat_command_groups_description:
self._print_command_group_description()
raw_command: str = Console().input(self._prompt)
try:
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except BaseInputCommandException as error:
with redirect_stdout(io.StringIO()) as f:
self._error_handler(error, raw_command)
res: str = f.getvalue()
self._print_framed_text(res)
continue
if self._is_exit_command(input_command):
self._autocompleter.exit_setup()
return
if self._is_unknown_command(input_command):
continue
with redirect_stdout(io.StringIO()) as f:
for registered_router in self._registered_routers:
registered_router.input_command_handler(input_command)
res: str = f.getvalue()
self._print_framed_text(res)
if not self._repeat_command_groups_description:
self._print_func(self._prompt)
def include_router(self, router: Router) -> None:
router.set_ignore_command_register(self._ignore_command_register)
self._registered_routers.add_registered_router(router)
def include_routers(self, *routers: Router) -> None:
for router in routers:
self.include_router(router)
def add_message_on_startup(self, message: str) -> None:
self._messages_on_startup.append(message)
-21
View File
@@ -1,21 +0,0 @@
from argenta.router import Router
class RegisteredRouters:
def __init__(self, registered_routers: list[Router] = None) -> None:
self._registered_routers = registered_routers if registered_routers else []
def get_registered_routers(self) -> list[Router]:
return self._registered_routers
def add_registered_router(self, router: Router):
self._registered_routers.append(router)
def add_registered_routers(self, *routers: Router):
self._registered_routers.extend(routers)
def __iter__(self):
return iter(self._registered_routers)
def __next__(self):
return next(iter(self._registered_routers))
-3
View File
@@ -1,3 +0,0 @@
__all__ = ["Command"]
from argenta.command.models import Command
-23
View File
@@ -1,23 +0,0 @@
from argenta.command.flag.models import InputFlag, Flag
class BaseInputCommandException(Exception):
pass
class UnprocessedInputFlagException(BaseInputCommandException):
def __str__(self):
return "Unprocessed Input Flags"
class RepeatedInputFlagsException(BaseInputCommandException):
def __init__(self, flag: Flag | InputFlag):
self.flag = flag
def __str__(self):
return ("Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'")
class EmptyInputCommandException(BaseInputCommandException):
def __str__(self):
return "Input Command is empty"
-4
View File
@@ -1,4 +0,0 @@
__all__ = ('InputFlags', 'InputFlag', 'Flag', 'Flags')
from argenta.command.flag.models import InputFlags, InputFlag, Flags, Flag
-21
View File
@@ -1,21 +0,0 @@
from dataclasses import dataclass
from argenta.command.flag.models import Flag
import re
@dataclass
class PredeterminedFlags:
HELP = Flag(name='help', possible_values=False)
SHORT_HELP = Flag(name='h', prefix='-', possible_values=False)
INFO = Flag(name='info', possible_values=False)
SHORT_INFO = Flag(name='i', prefix='-', possible_values=False)
ALL = Flag(name='all', possible_values=False)
SHORT_ALL = Flag(name='a', prefix='-', possible_values=False)
HOST = Flag(name='host', possible_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
SHORT_HOST = Flag(name='h', prefix='-', possible_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
PORT = Flag(name='port', possible_values=re.compile(r'^\d{1,5}$'))
SHORT_PORT = Flag(name='p', prefix='-', possible_values=re.compile(r'^\d{1,5}$'))
-140
View File
@@ -1,140 +0,0 @@
from typing import Literal, Pattern
from abc import ABC, abstractmethod
class BaseFlag:
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--'):
self._name = name
self._prefix = prefix
def get_string_entity(self):
string_entity: str = self._prefix + self._name
return string_entity
def get_name(self):
return self._name
def get_prefix(self):
return self._prefix
class InputFlag(BaseFlag):
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
value: str = None):
super().__init__(name, prefix)
self._flag_value = value
def get_value(self) -> str | None:
return self._flag_value
def set_value(self, value):
self._flag_value = value
class Flag(BaseFlag):
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
possible_values: list[str] | Pattern[str] | False = True):
super().__init__(name, prefix)
self.possible_values = possible_values
def validate_input_flag_value(self, input_flag_value: str | None):
if self.possible_values is False:
if input_flag_value is None:
return True
else:
return False
elif isinstance(self.possible_values, Pattern):
if isinstance(input_flag_value, str):
is_valid = bool(self.possible_values.match(input_flag_value))
if bool(is_valid):
return True
else:
return False
else:
return False
elif isinstance(self.possible_values, list):
if input_flag_value in self.possible_values:
return True
else:
return False
else:
return True
class BaseFlags(ABC):
__slots__ = ('_flags',)
@abstractmethod
def get_flags(self):
pass
@abstractmethod
def add_flag(self, flag: Flag | InputFlag):
pass
@abstractmethod
def add_flags(self, flags: list[Flag] | list[InputFlag]):
pass
@abstractmethod
def get_flag(self, name: str):
pass
def __iter__(self):
return iter(self._flags)
def __next__(self):
return next(iter(self))
def __getitem__(self, item):
return self._flags[item]
class Flags(BaseFlags, ABC):
def __init__(self, *flags: Flag):
self._flags = flags if flags else []
def get_flags(self) -> list[Flag]:
return self._flags
def add_flag(self, flag: Flag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> Flag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
class InputFlags(BaseFlags, ABC):
def __init__(self, *flags: InputFlag):
self._flags = flags if flags else []
def get_flags(self) -> list[InputFlag]:
return self._flags
def add_flag(self, flag: InputFlag):
self._flags.append(flag)
def add_flags(self, flags: list[InputFlag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> InputFlag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
-112
View File
@@ -1,112 +0,0 @@
from argenta.command.flag.models import Flag, InputFlag, Flags, InputFlags
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from typing import Generic, TypeVar, cast, Literal
InputCommandType = TypeVar('InputCommandType')
class BaseCommand:
def __init__(self, trigger: str):
self._trigger = trigger
def get_trigger(self) -> str:
return self._trigger
class Command(BaseCommand):
def __init__(self, trigger: str,
description: str = None,
flags: Flag | Flags = None,
aliases: list[str] = None):
super().__init__(trigger)
self._registered_flags: Flags = flags if isinstance(flags, Flags) else Flags(flags) if isinstance(flags, Flag) else Flags()
self._description = f'Description for "{self._trigger}" command' if not description else description
self._aliases = aliases
def get_registered_flags(self) -> Flags:
return self._registered_flags
def get_aliases(self) -> list[str] | None:
return self._aliases
def validate_input_flag(self, flag: InputFlag):
registered_flags: Flags | None = self.get_registered_flags()
if registered_flags:
if isinstance(registered_flags, Flag):
if registered_flags.get_string_entity() == flag.get_string_entity():
is_valid = registered_flags.validate_input_flag_value(flag.get_value())
if is_valid:
return True
else:
for registered_flag in registered_flags:
if registered_flag.get_string_entity() == flag.get_string_entity():
is_valid = registered_flag.validate_input_flag_value(flag.get_value())
if is_valid:
return True
return False
def get_description(self) -> str:
return self._description
class InputCommand(BaseCommand, Generic[InputCommandType]):
def __init__(self, trigger: str,
input_flags: InputFlag | InputFlags = None):
super().__init__(trigger)
self._input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags(input_flags) if isinstance(input_flags, InputFlag) else InputFlags()
def _set_input_flags(self, input_flags: InputFlags):
self._input_flags = input_flags
def get_input_flags(self) -> InputFlags:
return self._input_flags
@staticmethod
def parse(raw_command: str) -> InputCommandType:
if not raw_command:
raise EmptyInputCommandException()
list_of_tokens = raw_command.split()
command = list_of_tokens.pop(0)
input_flags: InputFlags = InputFlags()
current_flag_name, current_flag_value = None, None
for k, _ in enumerate(list_of_tokens):
if _.startswith('-'):
if current_flag_name or len(_) < 2 or len(_[:_.rfind('-')]) > 3:
raise UnprocessedInputFlagException()
current_flag_name = _
else:
if not current_flag_name:
raise UnprocessedInputFlagException()
current_flag_value = _
if current_flag_name:
if not len(list_of_tokens) == k+1:
if not list_of_tokens[k+1].startswith('-'):
continue
input_flag = InputFlag(name=current_flag_name[current_flag_name.rfind('-')+1:],
prefix=cast(Literal['-', '--', '---'],
current_flag_name[:current_flag_name.rfind('-')+1]),
value=current_flag_value)
all_flags = [flag.get_string_entity() for flag in input_flags.get_flags()]
if input_flag.get_string_entity() not in all_flags:
input_flags.add_flag(input_flag)
else:
raise RepeatedInputFlagsException(input_flag)
current_flag_name, current_flag_value = None, None
if any([current_flag_name, current_flag_value]):
raise UnprocessedInputFlagException()
else:
return InputCommand(trigger=command, input_flags=input_flags)
-4
View File
@@ -1,4 +0,0 @@
__all__ = ["Router"]
from argenta.router.entity import Router
-21
View File
@@ -1,21 +0,0 @@
from typing import Callable
from argenta.command import Command
from argenta.command.flag.models import InputFlags
class CommandHandler:
def __init__(self, handler: Callable[[], None] | Callable[[InputFlags], None], handled_command: Command):
self._handler = handler
self._handled_command = handled_command
def handling(self, input_flags: InputFlags = None):
if input_flags is not None:
self._handler(input_flags)
else:
self._handler()
def get_handler(self):
return self._handler
def get_handled_command(self):
return self._handled_command
-21
View File
@@ -1,21 +0,0 @@
from argenta.router.command_handler.entity import CommandHandler
class CommandHandlers:
def __init__(self, command_handlers: list[CommandHandler] = None):
self.command_handlers = command_handlers if command_handlers else []
def get_command_handlers(self) -> list[CommandHandler]:
return self.command_handlers
def add_command_handler(self, command_handler: CommandHandler):
self.command_handlers.append(command_handler)
def add_command_handlers(self, *command_handlers: CommandHandler):
self.command_handlers.extend(command_handlers)
def __iter__(self):
return iter(self.command_handlers)
def __next__(self):
return next(iter(self.command_handlers))
-5
View File
@@ -1,5 +0,0 @@
from argenta.router import Router
system_router = Router(title='System points:',
name='System')
-149
View File
@@ -1,149 +0,0 @@
from typing import Callable, Any
from inspect import getfullargspec
from argenta.command import Command
from argenta.command.models import InputCommand
from argenta.router.command_handlers.entity import CommandHandlers
from argenta.router.command_handler.entity import CommandHandler
from argenta.command.flag.models import Flag, Flags, InputFlags
from argenta.router.exceptions import (RepeatedFlagNameException,
TooManyTransferredArgsException,
RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
class Router:
def __init__(self,
title: str = None,
name: str = 'Default'):
self._title = title
self._name = name
self._command_handlers: CommandHandlers = CommandHandlers()
self._ignore_command_register: bool = False
self._not_valid_flag_handler: Callable[[Flag], None] = lambda flag: print(f"Undefined or incorrect input flag: {flag.get_string_entity()}{(' '+flag.get_value()) if flag.get_value() else ''}")
def command(self, command: Command) -> Callable[[Any], Any]:
self._validate_command(command)
def command_decorator(func):
Router._validate_func_args(command, func)
self._command_handlers.add_command_handler(CommandHandler(func, command))
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return command_decorator
def set_invalid_input_flag_handler(self, func):
processed_args = getfullargspec(func).args
if len(processed_args) != 1:
raise IncorrectNumberOfHandlerArgsException()
else:
self._not_valid_flag_handler = func
def input_command_handler(self, input_command: InputCommand):
input_command_name: str = input_command.get_trigger()
input_command_flags: InputFlags = input_command.get_input_flags()
for command_handler in self._command_handlers:
handle_command = command_handler.get_handled_command()
if input_command_name.lower() == handle_command.get_trigger().lower():
self._validate_input_command(input_command_flags, command_handler)
elif handle_command.get_aliases():
if input_command_name.lower() in handle_command.get_aliases():
self._validate_input_command(input_command_flags, command_handler)
def _validate_input_command(self, input_command_flags: InputFlags, command_handler: CommandHandler):
handle_command = command_handler.get_handled_command()
if handle_command.get_registered_flags().get_flags():
if input_command_flags.get_flags():
if self._validate_input_flags(handle_command, input_command_flags):
command_handler.handling(input_command_flags)
return
else:
command_handler.handling(input_command_flags)
return
else:
if input_command_flags.get_flags():
self._not_valid_flag_handler(input_command_flags[0])
return
else:
command_handler.handling()
return
def _validate_input_flags(self, handle_command: Command, input_flags: InputFlags):
for flag in input_flags:
is_valid = handle_command.validate_input_flag(flag)
if not is_valid:
self._not_valid_flag_handler(flag)
return False
return True
@staticmethod
def _validate_command(command: Command):
command_name: str = command.get_trigger()
if command_name.find(' ') != -1:
raise TriggerCannotContainSpacesException()
flags: Flags = command.get_registered_flags()
if flags:
flags_name: list = [x.get_string_entity().lower() for x in flags]
if len(set(flags_name)) < len(flags_name):
raise RepeatedFlagNameException()
@staticmethod
def _validate_func_args(command: Command, func: Callable):
registered_args = command.get_registered_flags()
transferred_args = getfullargspec(func).args
if registered_args.get_flags() and transferred_args:
if len(transferred_args) != 1:
raise TooManyTransferredArgsException()
elif registered_args.get_flags() and not transferred_args:
raise RequiredArgumentNotPassedException()
elif not registered_args.get_flags() and transferred_args:
raise TooManyTransferredArgsException()
def set_ignore_command_register(self, ignore_command_register: bool):
self._ignore_command_register = ignore_command_register
def get_triggers(self):
all_triggers: list[str] = []
for command_handler in self._command_handlers:
all_triggers.append(command_handler.get_handled_command().get_trigger())
return all_triggers
def get_aliases(self):
all_aliases: list[str] = []
for command_handler in self._command_handlers:
if command_handler.get_handled_command().get_aliases():
all_aliases.extend(command_handler.get_handled_command().get_aliases())
return all_aliases
def get_command_handlers(self) -> CommandHandlers:
return self._command_handlers
def get_name(self) -> str:
return self._name
def get_title(self) -> str | None:
return self._title
def set_title(self, title: str):
self._title = title
-23
View File
@@ -1,23 +0,0 @@
class RepeatedFlagNameException(Exception):
def __str__(self):
return "Repeated registered_flag name in register command"
class TooManyTransferredArgsException(Exception):
def __str__(self):
return "Too many transferred arguments"
class RequiredArgumentNotPassedException(Exception):
def __str__(self):
return "Required argument not passed"
class IncorrectNumberOfHandlerArgsException(Exception):
def __str__(self):
return "Handler has incorrect number of arguments"
class TriggerCannotContainSpacesException(Exception):
def __str__(self):
return "Command trigger cannot contain spaces"
@@ -0,0 +1,35 @@
<!--
A new scriv changelog fragment.
Uncomment the section that is right (remove the HTML comment wrapper).
For top level release notes, leave all the headers commented out.
-->
### Added
- A cli module that implements the ability to launch applications on Argenta, run application benchmarks on Argenta, create a boilerplate for new projects, and much more.
- A new `info` command has been added to the Argenta CLI, providing a quick overview of the installed package and runtime environment.
### Changed
- Refactoring the initialization order of some modules; heavy imports are now imported only when necessary, which resulted in a boost to importtime.
<!--
### Deprecated
- A bullet item for the Deprecated category.
-->
<!--
### Removed
- A bullet item for the Removed category.
-->
<!--
### Fixed
- A bullet item for the Fixed category.
-->
+3
View File
@@ -0,0 +1,3 @@
_build/
_static/
*.mo
+35
View File
@@ -0,0 +1,35 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
LANGUAGES = en ru
build-all:
sphinx-build -b html -D language=ru $(SOURCEDIR) $(BUILDDIR)/html/ru
sphinx-build -b html -D language=en $(SOURCEDIR) $(BUILDDIR)/html/en
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
live-ru:
sphinx-autobuild -b html . _build/html/ru -D language=ru
live-en:
sphinx-autobuild -b html . _build/html/en -D language=en
update-langs:
sphinx-build -b gettext . _build/gettext
sphinx-intl update -p _build/gettext -l en
.PHONY: help Makefile serve-all
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+17
View File
@@ -0,0 +1,17 @@
from argenta import App, Orchestrator
from argenta.orchestrator.argparser import ArgParser, BooleanArgument
arg_parser = ArgParser(
processed_args=[
BooleanArgument("dev")
]
)
orchestrator = Orchestrator(
arg_parser=arg_parser,
)
if __name__ == "__main__":
if arg_parser.parsed_argspace.get_by_name("dev"):
orchestrator.run_repl(App(initial_message="ArgentaDev"))
else:
orchestrator.run_repl(App())
+29
View File
@@ -0,0 +1,29 @@
from argenta import App, Orchestrator
from argenta.orchestrator.argparser import ArgParser, ValueArgument
arguments = [
ValueArgument("host", help="Server host", is_required=True),
ValueArgument("port", help="Server port", is_required=True),
]
argparser = ArgParser(processed_args=arguments, name="WebServer", description="Simple web server")
app = App()
orchestrator = Orchestrator(argparser)
def main():
argspace = argparser.parsed_argspace
host = argspace.get_by_name("host")
port = argspace.get_by_name("port")
print("Server configuration:")
print(f" Host: {host.value}")
print(f" Port: {port.value}")
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
+10
View File
@@ -0,0 +1,10 @@
from argenta import Response, Router
from argenta.di import FromDishka
from argenta.orchestrator.argparser import ArgSpace
router = Router()
@router.command("get_args")
async def get_args(response: Response, argspace: FromDishka[ArgSpace]):
print(argspace.all_arguments)
+21
View File
@@ -0,0 +1,21 @@
from argenta import Response, Router
from argenta.di import FromDishka
from argenta.orchestrator.argparser import ArgSpace, BooleanArgument, ValueArgument
router = Router()
@router.command("get_args")
def get_args(response: Response, argspace: FromDishka[ArgSpace]):
# Get all boolean flags
boolean_flags = argspace.get_by_type(BooleanArgument)
print(f"Active flags: {[arg.name for arg in boolean_flags if arg.value]}")
# Get all value arguments
value_args = argspace.get_by_type(ValueArgument)
for arg in value_args:
print(f"{arg.name} = {arg.value}")
# Count arguments of each type
print(f"Boolean arguments: {len(argspace.get_by_type(BooleanArgument))}")
print(f"Value arguments: {len(argspace.get_by_type(ValueArgument))}")
+20
View File
@@ -0,0 +1,20 @@
from argenta import Response, Router
from argenta.di import FromDishka
from argenta.orchestrator.argparser import ArgSpace
router = Router()
@router.command("get_args")
def get_args(response: Response, argspace: FromDishka[ArgSpace]):
config_arg = argspace.get_by_name("config")
if config_arg:
print(f"Config path: {config_arg.value}")
verbose_arg = argspace.get_by_name("verbose")
if verbose_arg and verbose_arg.value:
print("Verbose mode enabled")
unknown_arg = argspace.get_by_name("nonexistent")
if unknown_arg is None:
print("Argument not found")
+20
View File
@@ -0,0 +1,20 @@
from argenta.orchestrator.argparser import ArgParser, ValueArgument
# Create arguments
config_arg = ValueArgument("config", help="Path to configuration file", default="config.yaml")
log_level_arg = ValueArgument(
"log-level",
help="Logging level",
possible_values=["DEBUG", "INFO", "WARNING", "ERROR"],
default="INFO",
)
host_arg = ValueArgument("host", help="Server host address", is_required=True)
# Register in ArgParser
parser = ArgParser(
processed_args=[config_arg, log_level_arg, host_arg],
name="MyApp",
description="My application with CLI arguments",
)
+9
View File
@@ -0,0 +1,9 @@
from argenta.orchestrator.argparser import ArgParser, BooleanArgument
# Create boolean arguments
verbose_arg = BooleanArgument("verbose", help="Enable verbose output")
debug_arg = BooleanArgument("debug", help="Enable debug mode")
no_cache_arg = BooleanArgument("no-cache", help="Disable caching")
# Register in ArgParser
parser = ArgParser(processed_args=[verbose_arg, debug_arg, no_cache_arg], name="MyApp")
@@ -0,0 +1,10 @@
from argenta import App
from argenta.app import AutoCompleter
# Setting up autocompletion with saving history to a file
my_autocompleter = AutoCompleter(history_filename="argenta_history.txt")
# Passing the configured autocompleter to the application
app = App(autocompleter=my_autocompleter)
# ... the rest of the application logic
+20
View File
@@ -0,0 +1,20 @@
from argenta.command import Flag, Flags, Command
# Simple command without flags
hello_cmd = Command("hello", description="Greet the user")
# Command with description and aliases
quit_cmd = Command("quit", description="Exit the application", aliases=["exit", "q"])
# Command with flags
deploy_cmd = Command(
"deploy",
description="Deploy application to server",
flags=Flags(
[
Flag("env", possible_values=["dev", "prod"]),
Flag("force"),
]
),
aliases=["dep"],
)
+19
View File
@@ -0,0 +1,19 @@
from argenta import Command, Response, Router
router = Router(title="User Management")
@router.command(Command("create-user", description="Create a new user account"))
def handle_create_user(response: Response):
print("Creating new user...")
@router.command(
Command(
"delete-user",
description="Delete existing user account",
aliases=["remove-user", "rm-user"],
)
)
def handle_delete_user(response: Response):
print("Deleting user...")
+33
View File
@@ -0,0 +1,33 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
router = Router(title="Server Management")
@router.command(
Command(
"start",
description="Start the server",
flags=Flags(
[
Flag("port"),
Flag("host"),
Flag("debug"),
]
),
aliases=["run"],
)
)
def handle_start(response: Response):
input_flags = response.input_flags
port_flag = input_flags.get_flag_by_name("port")
host_flag = input_flags.get_flag_by_name("host")
debug_flag = input_flags.get_flag_by_name("debug")
host = host_flag.input_value if host_flag else "localhost"
port = port_flag.input_value if port_flag else "8080"
debug = debug_flag and debug_flag.input_value
print(f"Starting server on {host}:{port}")
if debug:
print("Debug mode: ON")
+11
View File
@@ -0,0 +1,11 @@
from argenta.command import InputCommand
# Parse command without flags
cmd1 = InputCommand.parse("hello")
print(cmd1.trigger) # "hello"
print(len(cmd1.input_flags)) # 0
# Parse command with flags
cmd2 = InputCommand.parse("deploy --env prod --force")
print(cmd2.trigger) # "deploy"
print(len(cmd2.input_flags)) # 2
+14
View File
@@ -0,0 +1,14 @@
from argenta import Router, Command, Response
router = Router(title="System")
@router.command(
Command(
"shutdown",
description="Shutdown the system",
aliases=["poweroff", "halt", "stop"]
)
)
def handle_shutdown(response: Response):
print("Shutting down the system...")
@@ -0,0 +1,9 @@
from sqlite3 import Connection
from argenta import Response, Router
from argenta.di import FromDishka
router = Router()
@router.command("connect")
def connect_handler(response: Response, connection: FromDishka[Connection]):
connection.execute("...")
@@ -0,0 +1,13 @@
import sqlite3
from sqlite3 import Connection
from typing import Iterable
from dishka import Provider, Scope, provide
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
conn = sqlite3.connect(":memory:")
yield conn
conn.close()
@@ -0,0 +1,3 @@
from argenta import Orchestrator
orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
@@ -0,0 +1,9 @@
from argenta import Response, Router
from argenta.di import FromDishka
from argenta.orchestrator.argparser import ArgSpace
router = Router()
@router.command("info")
def connect_handler(response: Response, argspace: FromDishka[ArgSpace]):
print(argspace.get_by_name("type"))
@@ -0,0 +1,7 @@
from argenta import App
def empty_command_handler():
print("Empty command handler called")
app: App = App()
app.set_empty_command_handler(empty_command_handler)
@@ -0,0 +1,7 @@
from argenta import App
def incorrect_input_syntax_handler(raw_command: str):
print(f"Incorrect input syntax for command: {raw_command}")
app: App = App()
app.set_incorrect_input_syntax_handler(incorrect_input_syntax_handler)
@@ -0,0 +1,7 @@
from argenta import App
def repeated_input_flags_handler(raw_command: str):
print(f"Repeated input flags: {raw_command}")
app: App = App()
app.set_repeated_input_flags_handler(repeated_input_flags_handler)
@@ -0,0 +1,7 @@
from argenta import App
def empty_command_handler():
print("Empty input command")
app: App = App()
app.set_empty_command_handler(empty_command_handler)
@@ -0,0 +1,8 @@
from argenta import App
from argenta.command import InputCommand
def unknown_command_handler(command: InputCommand):
print(f"Unknown input command with trigger: {command.trigger}")
app: App = App()
app.set_unknown_command_handler(unknown_command_handler)
@@ -0,0 +1,7 @@
from argenta import App, Response
def exit_command_handler(response: Response):
print("Exit command handler")
app: App = App()
app.set_exit_command_handler(exit_command_handler)
@@ -0,0 +1,36 @@
from argenta.command import Flags, PredefinedFlags
# Using predefined flags when creating a command
command_flags = Flags(
[
PredefinedFlags.HELP,
PredefinedFlags.SHORT_HELP,
PredefinedFlags.INFO,
]
)
# Using Network Flags
network_flags = Flags(
[
PredefinedFlags.HOST,
PredefinedFlags.PORT,
]
)
# Validating the values of predefined flags
print(PredefinedFlags.HOST.validate_input_flag_value("192.168.1.1")) # True
print(PredefinedFlags.HOST.validate_input_flag_value("invalid")) # False
print(PredefinedFlags.PORT.validate_input_flag_value("8080")) # True
print(PredefinedFlags.PORT.validate_input_flag_value("99999")) # True
print(PredefinedFlags.PORT.validate_input_flag_value("abc")) # False
# Flags without values
print(PredefinedFlags.HELP.validate_input_flag_value(None)) # True
print(PredefinedFlags.HELP.validate_input_flag_value("something")) # False
# Checking string representations
print(PredefinedFlags.HELP.string_entity) # --help
print(PredefinedFlags.SHORT_HELP.string_entity) # -H
print(PredefinedFlags.HOST.string_entity) # --host
print(PredefinedFlags.SHORT_PORT.string_entity) # -P
+20
View File
@@ -0,0 +1,20 @@
import re
from argenta.command import Flag, PossibleValues
# Simple flag with any values
verbose_flag = Flag(name="verbose")
# Flag with short prefix
short_flag = Flag(name="v", prefix="-")
# Flag that does not take a value
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
# Flag with list of possible values
format_flag = Flag(name="format", possible_values=["json", "xml", "csv"])
# Flag with regexp for validation input value
email_flag = Flag(
name="email",
possible_values=re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"),
)
+20
View File
@@ -0,0 +1,20 @@
import re
from argenta.command.flag.models import Flag, PossibleValues
# Flag with list of allowed values
format_flag = Flag(name="format", possible_values=["json", "xml", "csv"])
# Value validation
print(format_flag.validate_input_flag_value("json")) # True
print(format_flag.validate_input_flag_value("pdf")) # False
# Flag without value
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
print(help_flag.validate_input_flag_value(None)) # True
print(help_flag.validate_input_flag_value("value")) # False
# Flag with regular expression
port_flag = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
print(port_flag.validate_input_flag_value("8080")) # True
print(port_flag.validate_input_flag_value("abc")) # False
+9
View File
@@ -0,0 +1,9 @@
from argenta.command import Flag
help_flag = Flag(name="help")
version_flag = Flag(name="V", prefix="-")
print(help_flag) # --help
message = f"Use {help_flag} to see help"
print(message) # Use --help to see help
+12
View File
@@ -0,0 +1,12 @@
from argenta.command import Flag
verbose_flag = Flag(name="verbose", prefix="--")
short_flag = Flag(name="v", prefix="-")
# Debug presentation
print(repr(verbose_flag)) # Flag<prefix=--, name=verbose>
print(repr(short_flag)) # Flag<prefix=-, name=v>
# In an interactive console or debugger
# >>> verbose_flag
# Flag<prefix=--, name=verbose>
+21
View File
@@ -0,0 +1,21 @@
from argenta.command import Flag, PossibleValues
# Creating two flags with the same name and prefix
flag1 = Flag(name="verbose", prefix="--")
flag2 = Flag(name="verbose", prefix="--")
# Flag comparison
print(flag1 == flag2) # True
# Flags with different prefixes are not equal
flag3 = Flag(name="verbose", prefix="-")
print(flag1 == flag3) # False
# Flags with different names are not equal
flag4 = Flag(name="help", prefix="--")
print(flag1 == flag4) # False
# Different possible_values do not affect equality
flag5 = Flag(name="verbose", prefix="--", possible_values=PossibleValues.NEITHER)
flag6 = Flag(name="verbose", prefix="--", possible_values=["value1", "value2"])
print(flag5 == flag6)
@@ -0,0 +1,21 @@
from argenta import Router, Response
from argenta.command import Command, Flag, PossibleValues
from argenta.command.flag import ValidationStatus
router = Router()
@router.command(Command("deploy", flags=Flag("verbose", possible_values=PossibleValues.NEITHER)))
def deploy_handler(response: Response):
# Check for toggle flag presence
verbose_flag = response.input_flags.get_flag_by_name("verbose")
if verbose_flag and verbose_flag.status == ValidationStatus.VALID:
print("Deploying with verbose output...")
# Detailed logic
elif verbose_flag and verbose_flag.status == ValidationStatus.INVALID:
print("Incorrect flag value")
return
else:
print("Deploying...")
# Normal logic
+16
View File
@@ -0,0 +1,16 @@
from argenta import Router, Response
from argenta.command import Command, Flag
router = Router()
@router.command(Command("greet", flags=Flag("name")))
def greet_handler(response: Response):
# Get flag by name
name_flag = response.input_flags.get_flag_by_name("name")
# Check if flag was passed
if name_flag:
print(f"Hello, {name_flag.input_value}!")
else:
print("Hello, stranger!")
+11
View File
@@ -0,0 +1,11 @@
import re
from argenta.command import Command, Flag, Flags
flags = Flags(
[
Flag("host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")),
Flag("port", possible_values=re.compile(r"^\d{1,5}$")),
]
)
cmd = Command("start", description="Start the server", flags=flags)
+9
View File
@@ -0,0 +1,9 @@
from argenta.command import Flag, Flags
flags: Flags = Flags()
flags.add_flag(Flag("config"))
flags.add_flag(Flag("debug"))
flags.add_flag(Flag("log-level", possible_values=["INFO", "DEBUG", "ERROR"]))
print(len(flags)) # 3
+15
View File
@@ -0,0 +1,15 @@
from argenta.command import Flag, Flags
from argenta.command.flag.defaults import PredefinedFlags
flags = Flags([PredefinedFlags.HOST])
additional_flags = [
PredefinedFlags.PORT,
Flag("database"),
Flag("ssl"),
Flag("verbose"),
]
flags.add_flags(additional_flags)
print(len(flags)) # 5
+13
View File
@@ -0,0 +1,13 @@
from argenta.command import Flag, Flags
from argenta.command.flag.defaults import PredefinedFlags
flags = Flags([PredefinedFlags.HOST, PredefinedFlags.PORT, Flag("verbose")])
host_flag = flags.get_flag_by_name("host")
if host_flag:
print(f"Found flag: {host_flag.name}")
unknown_flag = flags.get_flag_by_name("nonexistent")
if unknown_flag is None:
print("Flag not found")
+13
View File
@@ -0,0 +1,13 @@
from argenta.command.flag import InputFlag, ValidationStatus
flag_with_value = InputFlag(
name="output", prefix="--", input_value="result.txt", status=ValidationStatus.VALID
)
flag_without_value = InputFlag(
name="help", prefix="-", input_value='', status=ValidationStatus.VALID
)
# String representation includes value
print(str(flag_with_value)) # --output result.txt
print(str(flag_without_value)) # -help
+12
View File
@@ -0,0 +1,12 @@
from argenta.command.flag import InputFlag, ValidationStatus
flag = InputFlag(
name="config",
prefix="--",
input_value="settings.json",
status=ValidationStatus.VALID,
)
# Debug representation of the object
print(repr(flag))
# InputFlag<prefix='--', name='config', value='settings.json', status=ValidationStatus.VALID>
@@ -0,0 +1,20 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
router = Router(title="Example")
@router.command(
Command(
"example",
description="Example command with flags",
flags=Flags([Flag("name"), Flag("age")]),
)
)
def example_handler(response: Response):
input_flags = response.input_flags
if input_flags:
print(f"Received {len(input_flags)} flag(s)")
else:
print("No flags provided")
@@ -0,0 +1,49 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags, InputFlag
from argenta.command.flag import ValidationStatus
router = Router(title="Comprehensive Example")
@router.command(
Command(
"validate",
description="Validate all flags",
flags=Flags(
[
Flag("format", possible_values=["json", "xml"]),
Flag("output"),
Flag("force"),
]
),
)
)
def validate_handler(response: Response):
input_flags = response.input_flags
print("Flag validation results:")
valid_flags: list[InputFlag] = []
invalid_flags: list[InputFlag] = []
undefined_flags: list[InputFlag] = []
for flag in input_flags:
if flag.status == ValidationStatus.VALID:
valid_flags.append(flag)
print(f"{flag.string_entity}: {flag.input_value} (VALID)")
elif flag.status == ValidationStatus.INVALID:
invalid_flags.append(flag)
print(f"{flag.string_entity}: {flag.input_value} (INVALID)")
elif flag.status == ValidationStatus.UNDEFINED:
undefined_flags.append(flag)
print(f" ? {flag.string_entity}: {flag.input_value} (UNDEFINED)")
print("\nSummary:")
print(f" Valid flags: {len(valid_flags)}")
print(f" Invalid flags: {len(invalid_flags)}")
print(f" Undefined flags: {len(undefined_flags)}")
if valid_flags:
print("\nProcessing valid flags:")
for flag in valid_flags:
print(f" Processing {flag.name} = {flag.input_value}")
@@ -0,0 +1,32 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
router = Router(title="Get Flag Example")
@router.command(
Command(
"config",
description="Configure settings",
flags=Flags([Flag("host"), Flag("port"), Flag("debug")]),
)
)
def config_handler(response: Response):
input_flags = response.input_flags
host_flag = input_flags.get_flag_by_name("host")
port_flag = input_flags.get_flag_by_name("port")
debug_flag = input_flags.get_flag_by_name("debug")
if host_flag:
print(f"Host: {host_flag.input_value}")
if port_flag:
print(f"Port: {port_flag.input_value}")
if debug_flag:
print("Debug mode enabled")
missing_flag = input_flags.get_flag_by_name("nonexistent")
if missing_flag is None:
print("Flag 'nonexistent' not found")
@@ -0,0 +1,20 @@
from argenta import Command, Response, Router
from argenta.command.flag import InputFlag, ValidationStatus
from argenta.command import InputFlags
router = Router(title="Add Flag Example")
@router.command(Command("test", description="Test command"))
def test_handler(response: Response):
# Create new InputFlags collection
new_flags = InputFlags()
# Add one flag
test_flag = InputFlag(
name="test", prefix="--", input_value="value", status=ValidationStatus.VALID
)
new_flags.add_flag(test_flag)
print(f"Flags count: {len(new_flags.flags)}")
print(f"First flag: {new_flags.flags[0].name}")
@@ -0,0 +1,19 @@
from argenta.command.flag import InputFlag, ValidationStatus
from argenta.command import InputFlags
# Create InputFlags collection
flags = InputFlags()
# Create several flags
flag1 = InputFlag(name="option1", prefix="--", input_value="value1", status=ValidationStatus.VALID)
flag2 = InputFlag(name="option2", prefix="--", input_value="value2", status=ValidationStatus.VALID)
flag3 = InputFlag(name="option3", prefix="---", input_value="value3", status=ValidationStatus.VALID)
# Add all flags in one call
flags.add_flags([flag1, flag2, flag3])
print(f"Total flags: {len(flags.flags)}")
for flag in flags:
print(f" - {flag.string_entity}: {flag.input_value}")
@@ -0,0 +1,27 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
router = Router(title="Bool Check Example")
@router.command(
Command(
"action",
description="Action with optional flags",
flags=Flags([Flag("option1"), Flag("option2")]),
)
)
def action_handler(response: Response):
input_flags = response.input_flags
# Check for flags presence
if input_flags:
print("Flags were provided:")
for flag in input_flags:
print(f" - {flag.name}: {flag.input_value}")
else:
print("No flags provided, using defaults")
# Alternative way to check
has_flags = bool(input_flags)
print(f"\nHas flags: {has_flags}")
@@ -0,0 +1,33 @@
from argenta.command.flag import InputFlag, ValidationStatus
from argenta.command import InputFlags
# Create first collection
flags1 = InputFlags(
[
InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID),
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
]
)
# Create second collection with same flags
flags2 = InputFlags(
[
InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID),
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
]
)
# Create third collection with different values
flags3 = InputFlags(
[
InputFlag(name="flag1", input_value="different", status=ValidationStatus.VALID),
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
]
)
print(f"flags1 == flags2: {flags1 == flags2}") # True (same names)
print(f"flags1 == flags3: {flags1 == flags3}") # True (same names, values are not considered)
# Different collections
flags4 = InputFlags([InputFlag(name="flag3", input_value="value3", status=ValidationStatus.VALID)])
print(f"flags1 == flags4: {flags1 == flags4}") # False (different flags)
@@ -0,0 +1,33 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
from argenta.command.flag import InputFlag
router = Router(title="Contains Example")
@router.command(
Command(
"check",
description="Check flags",
flags=Flags([Flag("verbose"), Flag("debug"), Flag("quiet")]),
)
)
def check_handler(response: Response):
input_flags = response.input_flags
# Check for specific flag presence
verbose_flag = input_flags.get_flag_by_name("verbose")
debug_flag = input_flags.get_flag_by_name("debug")
# Use 'in' operator for checking
if verbose_flag and verbose_flag in input_flags:
print("Verbose flag is present")
if debug_flag and debug_flag in input_flags:
print("Debug flag is present")
# You can create a flag for checking (comparison is by name)
test_flag = InputFlag(name="verbose", prefix="--", input_value="any", status=None)
if test_flag in input_flags:
print("Verbose flag found using 'in' operator")
@@ -0,0 +1,9 @@
from metrics.benchmarks.entity import benchmarks
@benchmarks.register(
type_="my_category",
description="Description of what is being measured"
)
def benchmark_my_operation() -> None:
# Code whose performance is being measured
pass
@@ -0,0 +1,27 @@
import sqlite3
from sqlite3 import Connection
from typing import Iterable
from dishka import Provider, Scope, provide
from argenta import App, Orchestrator
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
conn = sqlite3.connect(":memory:")
yield conn
conn.close()
# 2. Create and configure App
app = App()
# ... you can add routers here ...
# 3. Create Orchestrator, passing our provider
orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
# 4. Start the application
if __name__ == "__main__":
orchestrator.run_repl(app)
@@ -0,0 +1,5 @@
from argenta.command import Flag, PossibleValues
# Creating flags with any values
message_flag = Flag(name="message", possible_values=PossibleValues.ALL)
name_flag = Flag(name="name", possible_values=PossibleValues.ALL)
@@ -0,0 +1,14 @@
import re
from argenta.command import Flag, PossibleValues
# Flag without value
verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER)
# Flag with any value
output_flag = Flag(name="output", possible_values=PossibleValues.ALL)
# Flag with a list of valid values
format_flag = Flag(name="format", possible_values=["json", "xml", "csv", "yaml"])
# Flag with regular expression
email_flag = Flag(name="email", possible_values=re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$"))
@@ -0,0 +1,6 @@
from argenta.command import Flag, PossibleValues
# Creating flags without values
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER)
force_flag = Flag(name="force", possible_values=PossibleValues.NEITHER)
@@ -0,0 +1,67 @@
import operator
import re
from argenta import App, Orchestrator, Response, Router
from argenta.app import DynamicDividingLine
from argenta.command import Command, Flag, Flags
from argenta.response.status import ResponseStatus
router = Router("Calculator")
operations = {"mul": operator.mul, "sub": operator.sub, "add": operator.add}
@router.command(
Command(
"calc",
description="Calculator with two numbers",
flags=Flags(
[
Flag("a", possible_values=re.compile(r"^\d{,5}$")), # First number
Flag("b", possible_values=re.compile(r"^\d{,5}$")), # Second number
Flag(
"operation", possible_values=["add", "sub", "mul"]
), # Operation: add, sub, mul
]
),
)
)
def calc_handler(response: Response):
# Get flag values
a_flag = response.input_flags.get_flag_by_name("a")
b_flag = response.input_flags.get_flag_by_name("b")
op_flag = response.input_flags.get_flag_by_name("op")
# Check that all flags are provided
if response.status != ResponseStatus.ALL_FLAGS_VALID or not all([a_flag, b_flag, op_flag]):
print("Error: must specify --a, --b and --op")
return
a = float(a_flag.input_value)
b = float(b_flag.input_value)
operation = op_flag.input_value
try:
result = operations[operation](a, b)
except ZeroDivisionError:
print("Can't divide by zero")
else:
print(f"Result: {result}")
app = App(
initial_message="Calculator",
repeat_command_groups_printing=False,
prompt=">> ",
dividing_line=DynamicDividingLine("~"),
)
orchestrator = Orchestrator()
def main():
app.include_router(router)
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
+16
View File
@@ -0,0 +1,16 @@
# main.py
from .routers import router
from argenta import App, Orchestrator
app: App = App()
orchestrator: Orchestrator = Orchestrator()
def main() -> None:
app.include_router(router)
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
# routers.py
from argenta import Command, Response, Router
router = Router(title="Quickstart Example")
@router.command(Command("hello", description="Say hello"))
def handler(response: Response):
print("Hello, world!")
@@ -0,0 +1,33 @@
from argenta import App, Command, Orchestrator, Router, Response
from argenta.command import Flag
# 1. Create app and orchestrator instances
app = App(
prompt=">> ",
initial_message="Simple App",
farewell_message="Goodbye!",
repeat_command_groups_printing=False,
)
orchestrator = Orchestrator()
# 2. Create router for grouping commands
main_router = Router(title="Main commands")
# 3. Define command and its handler
@main_router.command(Command("hello", description="Prints greeting message", flags=Flag("name")))
def hello_handler(response: Response):
"""This handler will be called for 'hello' command."""
name = response.input_flags.get_flag_by_name("name")
if name:
print(f"Hello, {name.input_value}!")
else:
print("Hello, world!")
# 4. Include router to application
app.include_router(main_router)
# 5. Start application
if __name__ == "__main__":
orchestrator.run_repl(app)
@@ -0,0 +1,59 @@
from typing import cast
from argenta import Command, Response, Router
from argenta.command.flag import Flag, ValidationStatus
from argenta.command import Flags
from argenta.di import FromDishka
from .repository import Priority, Task, TaskRepository
router = Router(title="Task Manager")
@router.command(
Command(
"add-task",
description="Add a new task",
flags=Flags(
[
Flag("description"),
Flag("priority", possible_values=["low", "medium", "high"]),
]
),
)
)
def add_task(response: Response, repo: FromDishka[TaskRepository]):
description_flag = response.input_flags.get_flag_by_name("description")
if not description_flag or not description_flag.status == ValidationStatus.VALID:
print("Error: --description flag is required.")
return
task_description = description_flag.input_value or ""
priority_flag = response.input_flags.get_flag_by_name("priority")
if priority_flag and priority_flag.status == ValidationStatus.VALID:
priority_value = priority_flag.input_value
else:
priority_value = "medium"
priority = cast(Priority, priority_value)
task = Task(description=task_description, priority=priority)
repo.add_task(task)
print(f"Added task: '{task.description}' with priority '{task.priority}'")
@router.command(Command("list-tasks", description="List all tasks"))
def list_tasks(response: Response, repo: FromDishka[TaskRepository]):
tasks = repo.get_all_tasks()
if not tasks:
print("No tasks found.")
return
print("Tasks:")
for i, task in enumerate(tasks, 1):
print(f" {i}. {task.description} (Priority: {task.priority})")
@@ -0,0 +1,18 @@
from argenta import App, Orchestrator
from .handlers import router
from .provider import TaskProvider
# 1. Create app and orchestrator instances
app = App(
initial_message="Task Manager",
prompt="Enter a command: ",
)
orchestrator = Orchestrator(custom_providers=[TaskProvider()])
# 2. Include router with our commands
app.include_router(router)
# 3. Start polling via orchestrator
if __name__ == "__main__":
orchestrator.run_repl(app)
@@ -0,0 +1,9 @@
from dishka import Provider, Scope, provide
from .repository import TaskRepository
class TaskProvider(Provider):
@provide(scope=Scope.APP)
def get_repository(self) -> TaskRepository:
return TaskRepository()
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from typing import Literal
Priority = Literal["low", "medium", "high"]
@dataclass
class Task:
description: str
priority: Priority = "medium"
class TaskRepository:
def __init__(self):
self._tasks: list[Task] = []
def add_task(self, task: Task):
self._tasks.append(task)
def get_all_tasks(self) -> list[Task]:
return self._tasks
@@ -0,0 +1,10 @@
from argenta import Response, Router
# For this router stdout redirect will be disabled
interactive_router = Router(disable_redirect_stdout=True)
@interactive_router.command("ask")
def ask_name(response: Response):
name = input("What is your name? ")
print(f"Nice to meet you, {name}!")
@@ -0,0 +1,5 @@
from argenta import App
from argenta.app import StaticDividingLine
# All routers will use static line with length 50 by default
app = App(dividing_line=StaticDividingLine(length=50))
@@ -0,0 +1,43 @@
from argenta import Router, Response, Command, DataBridge
from argenta.command import Flag
from argenta.di import FromDishka
router = Router(title="Authentication")
def authenticate_user(username: str) -> str:
return f"token_for_{username}"
@router.command(Command("login", flags=Flag("username")))
def login_handler(response: Response, data_bridge: FromDishka[DataBridge]):
username_flag = response.input_flags.get_flag_by_name("username")
if not username_flag or not username_flag.input_value:
print("Error: username must be specified using the --username flag.")
return
username = username_flag.input_value
token = authenticate_user(username)
data_bridge.update({"auth_token": token})
print(f"Login successful! User '{username}' authenticated.")
@router.command("get-profile")
def get_profile_handler(response: Response, data_bridge: FromDishka[DataBridge]):
token = data_bridge.get_by_key("auth_token")
if not token:
print("Error: you are not authenticated. Please run the 'login' command first.")
return
print(f"Loading profile using token: [yellow]{token}[/yellow]")
@router.command("logout")
def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]):
try:
data_bridge.delete_by_key("auth_token")
print("Logout successful. Session data cleared.")
except KeyError:
print("You were not authenticated anyway.")
+12
View File
@@ -0,0 +1,12 @@
from argenta import Command, Response, Router
from argenta.response import ResponseStatus
router = Router(title="Example")
@router.command(Command("greet", description="Greet the user"))
def greet_handler(response: Response):
if response.status == ResponseStatus.ALL_FLAGS_VALID:
print("Hello! All flags are valid.")
else:
print("Warning: Some flags have issues.")
+35
View File
@@ -0,0 +1,35 @@
from argenta import Command, Response, Router
from argenta.command import Flag, Flags
from argenta.command.flag import ValidationStatus
from argenta.response import ResponseStatus
router = Router(title="Flags Example")
@router.command(
Command(
"process",
description="Process with flags",
flags=Flags([Flag("format", possible_values=["json", "xml"]), Flag("verbose")]),
)
)
def process_handler(response: Response):
print(f"Status: {response.status}")
format_flag = response.input_flags.get_flag_by_name("format")
verbose_flag = response.input_flags.get_flag_by_name("verbose")
if format_flag:
format_value = format_flag.input_value
print(f"Format: {format_value}")
if verbose_flag:
print("Verbose mode enabled")
if response.status == ResponseStatus.ALL_FLAGS_VALID:
print("All flags are valid, proceeding...")
elif response.status == ResponseStatus.INVALID_VALUE_FLAGS:
print("Warning: Some flags have invalid values")
for flag in response.input_flags:
if flag.status == ValidationStatus.INVALID:
print(f" Invalid flag: {flag.string_entity} = {flag.input_value}")
+7
View File
@@ -0,0 +1,7 @@
from argenta import Command, Response, Router
user_router = Router(title="User Management")
@user_router.command(Command("add-user", description="Adds a new user"))
def add_user_handler(response: Response):
print("User added successfully!")
@@ -0,0 +1,31 @@
import sys
from unittest.mock import patch
import pytest
from pytest import CaptureFixture
from argenta import App, Orchestrator, Router, Command, Response
@pytest.fixture(autouse=True)
def patched_argv():
with patch.object(sys, "argv", ["program.py"]):
yield
def test_input_incorrect_command(capsys: CaptureFixture[str]):
router = Router()
orchestrator = Orchestrator()
@router.command(Command("test"))
def test(response: Response) -> None:
print("test command")
app = App(override_system_messages=True, printer=print)
app.include_router(router)
app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.trigger}"))
with patch("builtins.input", side_effect=["help", "q"]):
orchestrator.run_repl(app)
output = capsys.readouterr().out
assert "\nUnknown command: help\n" in output

Some files were not shown because too many files have changed in this diff Show More