From 13055905a026a0b05f3f30d242631bfe8e19624b Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:59:38 -0500 Subject: [PATCH 1/6] feat: added --verbose flag to reduce the output to only when the user wants to see all the info. --@iamserda --- main.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 6e41231..20cede3 100644 --- a/main.py +++ b/main.py @@ -2,27 +2,31 @@ import argparse from dotenv import load_dotenv from google import genai +from google.genai import types load_dotenv() -api_key = os.environ.get("GEMINI_API_KEY") -gemini_client = genai.Client(api_key=api_key) -def main(): +def main(api_key: str): + gemini_client = genai.Client(api_key=api_key) if api_key is None: raise RuntimeError("Please check your secrets' source file. A key with the value provided was not found.") + parser = argparse.ArgumentParser( prog="gemini-client", usage="Enter your prompt here: ", description="Takes your question or prompt, and returns Gemini 2.5 Flash's response to the prompt", ) parser.add_argument("user_prompt", type=str, help="Enter your prompt or questions for Gemini AI here: ") + parser.add_argument("--verbose", action="store_true", help="Enable verbose output") args = parser.parse_args() _prompt = args.user_prompt + messages = [types.Content(role="user", parts=[types.Part(text=args.user_prompt)])] response = gemini_client.models.generate_content( model="gemini-2.5-flash", - contents=args.user_prompt, + contents=messages, ) + if response is None or response.usage_metadata is None: raise RuntimeError("No response was provide. Please check configuration and connection and try again!") @@ -31,14 +35,17 @@ def main(): response.usage_metadata.candidates_token_count, response.text, ) - - results = f""" + response = f"Response: {gemini_response}" + verbose_response = f""" User prompt: {_prompt} Prompt tokens: {req_token_count} Response tokens: {res_token_count} -Response: -{gemini_response}""" - print(results) +{response}""" + if args.verbose: + print(verbose_response) + else: + print(response) if __name__ == "__main__": - main() + api_key = os.environ.get("GEMINI_API_KEY") + main(api_key=api_key) From 6139d3273b124e21a6f511477276b28866303d27 Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:04:27 -0500 Subject: [PATCH 2/6] refactor: updated example for the .env file with proper expected env-var-name, updated gitignore file to better reflect the name cases of files to avoid issue in different operating systems, added some make recipes, added some dependencies like ruff and pre-commit, added config file for pre-commits hooks and synced uv.lock --@iamserda --- .env-example | 3 +- .gitignore | 6 +- .pre-commit-config.yaml | 18 +++++ Makefile | 18 +++++ README.md | 3 +- pyproject.toml | 2 + uv.lock | 159 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 Makefile diff --git a/.env-example b/.env-example index 50daa19..603eb86 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,5 @@ -GEMINI_API_KEY="replace-this-with-your-api-key" +GEMINI_API_KEY=your-key-here + # NOTES: # Please DO NOT add your secret here. This file is being tracked by git. diff --git a/.gitignore b/.gitignore index 363f155..670610c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,8 @@ wheels/ # Virtual environments .venv -.ENV \ No newline at end of file +.ENV +**/*.env +.env.* +!.env.example +temp.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0dc55d1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.15.1 + hooks: + # Run the linter. + - id: ruff-check + types_or: [ python, pyi ] + args: [ --fix] + # Run the formatter. + - id: ruff-format + types_or: [ python, pyi ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e742480 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +SHELL ?= /bin/zsh +VENV ?= ".venv" +PROMPT ?="Who is the current president of the United States of America?" + +runp: + uv run python main.py "$(PROMPT)" + +prompt: + uv run python main.py "$(PROMPT)" + +lint: + uv run pre-commit run ruff-check + +fmt format: + uv run pre-commit run ruff-format + +precommit: lint fmt + @echo $("ready to commit!") diff --git a/README.md b/README.md index 628eeb6..c9438bf 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A crude, local-first take on Claude Code, wired into Google’s Gemini API for c ## Getting started 1) Install Python 3.12+ and create a virtualenv. 2) Add your Gemini key to `.env`: - - `GOOGLE_API_KEY=your-key-here` + - `GEMINI_API_KEY=your-google-gemini-generated-api-key` 3) Install deps (uses `uv`; swap for pip if you prefer): - `uv sync` @@ -20,4 +20,3 @@ A crude, local-first take on Claude Code, wired into Google’s Gemini API for c - Teach the agent to read/write files and summarize diffs. - Add prompt presets for “explain”, “review”, “refactor”. - Wire in tests around the Gemini client and prompt formatting. - diff --git a/pyproject.toml b/pyproject.toml index 9c43077..85aba19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,5 +6,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "google-genai==1.12.1", + "pre-commit>=4.5.1", "python-dotenv==1.1.0", + "ruff>=0.15.1", ] diff --git a/uv.lock b/uv.lock index 3a78398..ac7c88e 100644 --- a/uv.lock +++ b/uv.lock @@ -8,13 +8,17 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "google-genai" }, + { name = "pre-commit" }, { name = "python-dotenv" }, + { name = "ruff" }, ] [package.metadata] requires-dist = [ { name = "google-genai", specifier = "==1.12.1" }, + { name = "pre-commit", specifier = ">=4.5.1" }, { name = "python-dotenv", specifier = "==1.1.0" }, + { name = "ruff", specifier = ">=0.15.1" }, ] [[package]] @@ -48,6 +52,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -105,6 +118,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/cd/fa3ab025a8f9772e8a9146d8fd8eef6d62649274d231ca84249f54a0de4a/filelock-3.24.0.tar.gz", hash = "sha256:aeeab479339ddf463a1cdd1f15a6e6894db976071e5883efc94d22ed5139044b", size = 37166, upload-time = "2026-02-14T16:05:28.723Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/dd/d7e7f4f49180e8591c9e1281d15ecf8e7f25eb2c829771d9682f1f9fe0c8/filelock-3.24.0-py3-none-any.whl", hash = "sha256:eebebb403d78363ef7be8e236b63cc6760b0004c7464dceaba3fd0afbd637ced", size = 23977, upload-time = "2026-02-14T16:05:27.578Z" }, +] + [[package]] name = "google-auth" version = "2.47.0" @@ -173,6 +204,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "identify" +version = "2.6.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -182,6 +222,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/d5/763666321efaded11112de8b7a7f2273dd8d1e205168e73c334e54b0ab9a/platformdirs-4.9.1.tar.gz", hash = "sha256:f310f16e89c4e29117805d8328f7c10876eeff36c94eac879532812110f7d39f", size = 28392, upload-time = "2026-02-14T21:02:44.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/77/e8c95e95f1d4cdd88c90a96e31980df7e709e51059fac150046ad67fac63/platformdirs-4.9.1-py3-none-any.whl", hash = "sha256:61d8b967d34791c162d30d60737369cbbd77debad5b981c4bfda1842e71e0d66", size = 21307, upload-time = "2026-02-14T21:02:43.492Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "pyasn1" version = "0.6.2" @@ -298,6 +372,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + [[package]] name = "requests" version = "2.32.5" @@ -325,6 +445,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, ] +[[package]] +name = "ruff" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -355,6 +500,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "virtualenv" +version = "20.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, +] + [[package]] name = "websockets" version = "15.0.1" From 66ec2eb32c205347930bc89590a5f65af53eb299 Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:06:17 -0500 Subject: [PATCH 3/6] feat: added calculator app functionality which will be the app our code-agent will work on and added unit tests for various operations for the calculator app --@iamserda --- calculator/main.py | 29 +++++++++++++++++++++++++++ calculator/tests.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ main.py | 46 +++++++++++++++++++++++++----------------- 3 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 calculator/main.py create mode 100644 calculator/tests.py diff --git a/calculator/main.py b/calculator/main.py new file mode 100644 index 0000000..a363d8f --- /dev/null +++ b/calculator/main.py @@ -0,0 +1,29 @@ +# calculator/main.py + +import sys +from pkg.calculator import Calculator +from pkg.render import format_json_output + + +def main(): + calculator = Calculator() + if len(sys.argv) <= 1: + print("Calculator App") + print('Usage: python main.py ""') + print('Example: python main.py "3 + 5"') + return + + expression = " ".join(sys.argv[1:]) + try: + result = calculator.evaluate(expression) + if result is not None: + to_print = format_json_output(expression, result) + print(to_print) + else: + print("Error: Expression is empty or contains only whitespace.") + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + main() diff --git a/calculator/tests.py b/calculator/tests.py new file mode 100644 index 0000000..87cec89 --- /dev/null +++ b/calculator/tests.py @@ -0,0 +1,49 @@ +# calculator/tests.py + +import unittest +from pkg.calculator import Calculator + + +class TestCalculator(unittest.TestCase): + def setUp(self): + self.calculator = Calculator() + + def test_addition(self): + result = self.calculator.evaluate("3 + 5") + self.assertEqual(result, 8) + + def test_subtraction(self): + result = self.calculator.evaluate("10 - 4") + self.assertEqual(result, 6) + + def test_multiplication(self): + result = self.calculator.evaluate("3 * 4") + self.assertEqual(result, 12) + + def test_division(self): + result = self.calculator.evaluate("10 / 2") + self.assertEqual(result, 5) + + def test_nested_expression(self): + result = self.calculator.evaluate("3 * 4 + 5") + self.assertEqual(result, 17) + + def test_complex_expression(self): + result = self.calculator.evaluate("2 * 3 - 8 / 2 + 5") + self.assertEqual(result, 7) + + def test_empty_expression(self): + result = self.calculator.evaluate("") + self.assertIsNone(result) + + def test_invalid_operator(self): + with self.assertRaises(ValueError): + self.calculator.evaluate("$ 3 5") + + def test_not_enough_operands(self): + with self.assertRaises(ValueError): + self.calculator.evaluate("+ 3") + + +if __name__ == "__main__": + unittest.main() diff --git a/main.py b/main.py index 20cede3..5dca84f 100644 --- a/main.py +++ b/main.py @@ -5,38 +5,48 @@ from google.genai import types load_dotenv() +API_KEY: str | None = os.environ.get("GEMINI_API_KEY") -def main(api_key: str): - gemini_client = genai.Client(api_key=api_key) - if api_key is None: - raise RuntimeError("Please check your secrets' source file. A key with the value provided was not found.") - +def main(): + if API_KEY is None: + raise RuntimeError( + "Please check your secrets' source file. A key with the value provided was not found." + ) + gemini_client = genai.Client(api_key=API_KEY) parser = argparse.ArgumentParser( prog="gemini-client", usage="Enter your prompt here: ", description="Takes your question or prompt, and returns Gemini 2.5 Flash's response to the prompt", ) - parser.add_argument("user_prompt", type=str, help="Enter your prompt or questions for Gemini AI here: ") + parser.add_argument( + "user_prompt", + type=str, + help="Enter your prompt or questions for Gemini AI here: ", + ) parser.add_argument("--verbose", action="store_true", help="Enable verbose output") args = parser.parse_args() _prompt = args.user_prompt - messages = [types.Content(role="user", parts=[types.Part(text=args.user_prompt)])] - response = gemini_client.models.generate_content( + messages: list[types.Content] = [ + types.Content(role="user", parts=[types.Part(text=args.user_prompt)]) + ] + response: genai.GenerateContentResponse = gemini_client.models.generate_content( # type: ignore model="gemini-2.5-flash", contents=messages, ) - if response is None or response.usage_metadata is None: - raise RuntimeError("No response was provide. Please check configuration and connection and try again!") + if response is None or response.usage_metadata is None: # type: ignore + raise RuntimeError( + "No response was provide. Please check configuration and connection and try again!" + ) - req_token_count, res_token_count, gemini_response = ( - response.usage_metadata.prompt_token_count, - response.usage_metadata.candidates_token_count, - response.text, + req_token_count, res_token_count, gemini_response = ( # type: ignore + response.usage_metadata.prompt_token_count, # type: ignore + response.usage_metadata.candidates_token_count, # type: ignore + response.text, # type: ignore ) - response = f"Response: {gemini_response}" - verbose_response = f""" + response: str = f"Response: {gemini_response}" + verbose_response: str = f""" User prompt: {_prompt} Prompt tokens: {req_token_count} Response tokens: {res_token_count} @@ -46,6 +56,6 @@ def main(api_key: str): else: print(response) + if __name__ == "__main__": - api_key = os.environ.get("GEMINI_API_KEY") - main(api_key=api_key) + main() From 36deaec85140fdd9fb4ec6c6fb40b8084c6863ae Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:04:00 -0500 Subject: [PATCH 4/6] feat: implement calculator functionality with evaluation logic and JSON output formatting; add file retrieval utility and corresponding tes, updated make, added basic and proper unit testing for path to folder llm will be allowed to work on. updated readme, makefile, gitignore --@iamserda --- .gitignore | 150 +++++++++++++++++++++++++++++++++++ Makefile | 21 ++++- README.md | 32 ++++++++ calculator/__init__.py | 0 calculator/pkg/calculator.py | 62 +++++++++++++++ calculator/pkg/render.py | 16 ++++ functions/get_files_info.py | 62 +++++++++++++++ functions/tests.py | 60 ++++++++++++++ test_get_files_info.py | 21 +++++ 9 files changed, 420 insertions(+), 4 deletions(-) create mode 100644 calculator/__init__.py create mode 100644 calculator/pkg/calculator.py create mode 100644 calculator/pkg/render.py create mode 100644 functions/get_files_info.py create mode 100644 functions/tests.py create mode 100644 test_get_files_info.py diff --git a/.gitignore b/.gitignore index 670610c..d1f1dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,153 @@ wheels/ .env.* !.env.example temp.py + +#macos file +**/*.DS_Store + +#workspace +.vscode +temp.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +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/ + +# 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 +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.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 + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +!*/frontend/.env +.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/ + +.idea/ +recipe_client +example.db + +# ignoring secrets file +.env + +# cache +**/*.ruff_cache +**/engineering_notes.md +**/*.db diff --git a/Makefile b/Makefile index e742480..dcc122c 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,19 @@ SHELL ?= /bin/zsh VENV ?= ".venv" +VENV_ACTIVATE ?= $(VENV)/bin/activate PROMPT ?="Who is the current president of the United States of America?" -runp: +env: + @echo "To activate your env, run:" + @echo 'eval "$$(make -s activate)"' + +activate: + @echo "source $(VENV_ACTIVATE)" + +run: uv run python main.py "$(PROMPT)" -prompt: +run-with-prompt: uv run python main.py "$(PROMPT)" lint: @@ -14,5 +22,10 @@ lint: fmt format: uv run pre-commit run ruff-format -precommit: lint fmt - @echo $("ready to commit!") +test-basic: + uv run test_get_files_info.py +make test: + uv run functions/tests.py + +precommit: lint fmt test + @echo "✅ ready to commit!" diff --git a/README.md b/README.md index c9438bf..433c738 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,39 @@ A crude, local-first take on Claude Code, wired into Google’s Gemini API for c - `uv sync` - `uv run python main.py` +## Makefile recipes + +### `make env` +Prints the shell command to activate the virtual environment. + +### `make activate` +Prints the `source` command for activating the virtual environment. + +### `make run-with-prompt` +Runs `main.py` with the prompt defined by `PROMPT`. + +### `make lint` +Runs the Ruff lint check via pre-commit. + +### `make fmt` / `make format` +Formats code via Ruff format through pre-commit. + +### `make precommit` +Runs `lint`, `fmt`, and `test` in sequence. + ## Next steps - Teach the agent to read/write files and summarize diffs. - Add prompt presets for “explain”, “review”, “refactor”. - Wire in tests around the Gemini client and prompt formatting. +## Contributing +Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. + +## License +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Author +Created by [@iamserda](https://iamserda.github.io) - [GitHub](https://github.com/iamserda) + +## Acknowledgments +- Powered by [Google Gemini API](https://ai.google.dev/) for code-aware responses +- Project scaffolding with [uv](https://docs.astral.sh/uv/) and [Ruff](https://docs.astral.sh/ruff/) diff --git a/calculator/__init__.py b/calculator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/calculator/pkg/calculator.py b/calculator/pkg/calculator.py new file mode 100644 index 0000000..a7e857c --- /dev/null +++ b/calculator/pkg/calculator.py @@ -0,0 +1,62 @@ +# calculator/pkg/calculator.py + + +class Calculator: + def __init__(self): + self.operators = { + "+": lambda a, b: a + b, + "-": lambda a, b: a - b, + "*": lambda a, b: a * b, + "/": lambda a, b: a / b, + } + self.precedence = { + "+": 1, + "-": 1, + "*": 2, + "/": 2, + } + + def evaluate(self, expression): + if not expression or expression.isspace(): + return None + tokens = expression.strip().split() + return self._evaluate_infix(tokens) + + def _evaluate_infix(self, tokens): + values = [] + operators = [] + + for token in tokens: + if token in self.operators: + while ( + operators + and operators[-1] in self.operators + and self.precedence[operators[-1]] >= self.precedence[token] + ): + self._apply_operator(operators, values) + operators.append(token) + else: + try: + values.append(float(token)) + except ValueError: + raise ValueError(f"invalid token: {token}") + + while operators: + self._apply_operator(operators, values) + + if len(values) != 1: + raise ValueError("invalid expression") + + return values[0] + + def _apply_operator(self, operators, values): + if not operators: + return + + operator = operators.pop() + if len(values) < 2: + raise ValueError(f"not enough operands for operator {operator}") + + b = values.pop() + a = values.pop() + values.append(self.operators[operator](a, b)) diff --git a/calculator/pkg/render.py b/calculator/pkg/render.py new file mode 100644 index 0000000..b3cb28d --- /dev/null +++ b/calculator/pkg/render.py @@ -0,0 +1,16 @@ +# calculator/pkg/render.py + +import json + + +def format_json_output(expression: str, result: float, indent: int = 2) -> str: + if isinstance(result, float) and result.is_integer(): + result_to_dump = int(result) + else: + result_to_dump = result + + output_data = { + "expression": expression, + "result": result_to_dump, + } + return json.dumps(output_data, indent=indent) diff --git a/functions/get_files_info.py b/functions/get_files_info.py new file mode 100644 index 0000000..d12d0fa --- /dev/null +++ b/functions/get_files_info.py @@ -0,0 +1,62 @@ +import os +from pathlib import Path + + +def get_files_info(working_directory, directory=".") -> str: + try: + if working_directory is None: + return f'Error: "{working_directory}" is not a directory' + + result = "Result for current directory:" + abs_work_dir = os.path.abspath(working_directory) + target_dir = os.path.normpath(os.path.join(abs_work_dir, directory)) + is_valid_target = ( + os.path.commonpath(paths=[abs_work_dir, target_dir]) == abs_work_dir + ) + + if not is_valid_target: + return ( + f"Result for '{directory}' directory:\n" + + f'\tError: Cannot list "{directory}" as it is outside the permitted working directory' + ) + if not os.path.isdir(target_dir): + return result + f'\nError: "{directory}" is not a directory' + + result_components = [] + get_files(Path(target_dir), result_components) + + if result_components: + for component in result_components: + result += f"\n - {component}" + return result + except Exception as exeption: + return f"Error: {exeption}" + + +def get_files(target_dir=Path("."), result_components: list = []): + target_dir_path = Path(target_dir) + for elem in Path.iterdir(target_dir_path): + elem_stat = elem.stat() + if elem.is_file(): + result_components.append( + f"{elem.name}: file_size: {elem_stat.st_size}, is_dir={elem.is_dir()}" + ) + elif elem.is_dir(): + result_components.append( + f"{elem.name}: file_size: {elem_stat.st_size}, is_dir={elem.is_dir()}" + ) + get_files(target_dir=elem, result_components=result_components) + + +if __name__ == "__main__": + actual_result = get_files_info(working_directory="calculator", directory=".") + print("\n", actual_result) + + actual_result = get_files_info(working_directory="calculator", directory="pkg/") + print("\n", actual_result) + + actual_result = get_files_info(working_directory="calculator", directory="/bin") + print(actual_result) + + actual_result = get_files_info(working_directory="calculator", directory="../") + print(actual_result) diff --git a/functions/tests.py b/functions/tests.py new file mode 100644 index 0000000..7657959 --- /dev/null +++ b/functions/tests.py @@ -0,0 +1,60 @@ +import unittest +from get_files_info import get_files_info + + +# expected_answer = ( +# 'Error: Cannot list "/bin" as it is outside the permitted working directory' +# ) +# assert ( +# get_files_info(working_directory="calculator", directory="/bin") == expected_answer +# ) + +# expected_answer = ( +# 'Error: Cannot list "../" as it is outside the permitted working directory' +# ) +# assert get_files_info(working_directory="/bin", directory=".") == expected_answer + + +class TestGetFilesInfo(unittest.TestCase): + def setUp(self) -> None: + self.get_file_info = get_files_info + + def test_known_valid_path1(self): + expected_answer = """Result for current directory: + - __init__.py: file_size: 0, is_dir=False + - tests.py: file_size: 1354, is_dir=False + - main.py: file_size: 741, is_dir=False + - pkg: file_size: 128, is_dir=True + - render.py: file_size: 404, is_dir=False + - calculator.py: file_size: 1754, is_dir=False""" + working_dir = "calculator" + dir = "." + actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) + self.assertEqual(actual_answer, expected_answer) + + def test_known_valid_path2(self): + expected_answer = """Result for current directory: + - render.py: file_size: 404, is_dir=False + - calculator.py: file_size: 1754, is_dir=False""" + working_dir = "calculator" + dir = "pkg" + actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) + self.assertEqual(actual_answer, expected_answer) + + def test_known_invalid_path_raises_error(self): + dir = "/bin" + working_dir = "calculator" + expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) + self.assertEqual(actual_answer, expected_answer) + + def test_known_valid_path_raises_error(self): + dir = "../" + working_dir = "calculator" + expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) + self.assertEqual(actual_answer, expected_answer) + + +if __name__ == "__main__": + unittest.main() diff --git a/test_get_files_info.py b/test_get_files_info.py new file mode 100644 index 0000000..bd0e643 --- /dev/null +++ b/test_get_files_info.py @@ -0,0 +1,21 @@ +from functions.get_files_info import get_files_info + + +if __name__ == "__main__": + dir = "." + actual_result = get_files_info(working_directory="calculator", directory=dir) + print("\n", actual_result) + + dir = "pkg" + actual_result = get_files_info(working_directory="calculator", directory=dir) + print("\n", actual_result) + + dir = "/bin" + expected_result = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_result = get_files_info(working_directory="calculator", directory="/bin") + print(actual_result) + + dir = "../" + expected_result = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_result = get_files_info(working_directory="calculator", directory="../") + print(actual_result) From a7c1d4ecbb9360793aec2e34b8fd0e0a07547530 Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:43:27 -0500 Subject: [PATCH 5/6] feat: update testing framework and add new test cases for get_files_info; modify Makefile to use pytest --@iamserda --- Makefile | 2 +- functions/tests.py | 60 ------------------ ...et_files_info.py => get_files_info_test.py | 0 pyproject.toml | 1 + tests/test_get_files_info.py | 39 ++++++++++++ uv.lock | 63 +++++++++++++++++++ 6 files changed, 104 insertions(+), 61 deletions(-) rename test_get_files_info.py => get_files_info_test.py (100%) create mode 100644 tests/test_get_files_info.py diff --git a/Makefile b/Makefile index dcc122c..1b92aed 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ fmt format: test-basic: uv run test_get_files_info.py make test: - uv run functions/tests.py + uv run pytest -q precommit: lint fmt test @echo "✅ ready to commit!" diff --git a/functions/tests.py b/functions/tests.py index 7657959..e69de29 100644 --- a/functions/tests.py +++ b/functions/tests.py @@ -1,60 +0,0 @@ -import unittest -from get_files_info import get_files_info - - -# expected_answer = ( -# 'Error: Cannot list "/bin" as it is outside the permitted working directory' -# ) -# assert ( -# get_files_info(working_directory="calculator", directory="/bin") == expected_answer -# ) - -# expected_answer = ( -# 'Error: Cannot list "../" as it is outside the permitted working directory' -# ) -# assert get_files_info(working_directory="/bin", directory=".") == expected_answer - - -class TestGetFilesInfo(unittest.TestCase): - def setUp(self) -> None: - self.get_file_info = get_files_info - - def test_known_valid_path1(self): - expected_answer = """Result for current directory: - - __init__.py: file_size: 0, is_dir=False - - tests.py: file_size: 1354, is_dir=False - - main.py: file_size: 741, is_dir=False - - pkg: file_size: 128, is_dir=True - - render.py: file_size: 404, is_dir=False - - calculator.py: file_size: 1754, is_dir=False""" - working_dir = "calculator" - dir = "." - actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) - self.assertEqual(actual_answer, expected_answer) - - def test_known_valid_path2(self): - expected_answer = """Result for current directory: - - render.py: file_size: 404, is_dir=False - - calculator.py: file_size: 1754, is_dir=False""" - working_dir = "calculator" - dir = "pkg" - actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) - self.assertEqual(actual_answer, expected_answer) - - def test_known_invalid_path_raises_error(self): - dir = "/bin" - working_dir = "calculator" - expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" - actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) - self.assertEqual(actual_answer, expected_answer) - - def test_known_valid_path_raises_error(self): - dir = "../" - working_dir = "calculator" - expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" - actual_answer = self.get_file_info(working_directory=working_dir, directory=dir) - self.assertEqual(actual_answer, expected_answer) - - -if __name__ == "__main__": - unittest.main() diff --git a/test_get_files_info.py b/get_files_info_test.py similarity index 100% rename from test_get_files_info.py rename to get_files_info_test.py diff --git a/pyproject.toml b/pyproject.toml index 85aba19..f70b7d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.12" dependencies = [ "google-genai==1.12.1", "pre-commit>=4.5.1", + "pytest>=9.0.2", "python-dotenv==1.1.0", "ruff>=0.15.1", ] diff --git a/tests/test_get_files_info.py b/tests/test_get_files_info.py new file mode 100644 index 0000000..0d55a01 --- /dev/null +++ b/tests/test_get_files_info.py @@ -0,0 +1,39 @@ +from functions.get_files_info import get_files_info + + +class TestGetFilesInfo: + def test_known_valid_path1(self): + expected_answer = """Result for current directory: + - __init__.py: file_size: 0, is_dir=False + - tests.py: file_size: 1354, is_dir=False + - main.py: file_size: 741, is_dir=False + - pkg: file_size: 128, is_dir=True + - render.py: file_size: 404, is_dir=False + - calculator.py: file_size: 1754, is_dir=False""" + working_dir = "calculator" + dir = "." + actual_answer = get_files_info(working_directory=working_dir, directory=dir) + assert actual_answer == expected_answer + + def test_known_valid_path2(self): + expected_answer = """Result for current directory: + - render.py: file_size: 404, is_dir=False + - calculator.py: file_size: 1754, is_dir=False""" + working_dir = "calculator" + dir = "pkg" + actual_answer = get_files_info(working_directory=working_dir, directory=dir) + assert actual_answer == expected_answer + + def test_known_invalid_path_raises_error(self): + dir = "/bin" + working_dir = "calculator" + expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_answer = get_files_info(working_directory=working_dir, directory=dir) + assert actual_answer == expected_answer + + def test_known_valid_path_raises_error(self): + dir = "../" + working_dir = "calculator" + expected_answer = f"Result for '{dir}' directory:\n\tError: Cannot list \"{dir}\" as it is outside the permitted working directory" + actual_answer = get_files_info(working_directory=working_dir, directory=dir) + assert actual_answer == expected_answer diff --git a/uv.lock b/uv.lock index ac7c88e..6bbb18d 100644 --- a/uv.lock +++ b/uv.lock @@ -9,6 +9,7 @@ source = { virtual = "." } dependencies = [ { name = "google-genai" }, { name = "pre-commit" }, + { name = "pytest" }, { name = "python-dotenv" }, { name = "ruff" }, ] @@ -17,6 +18,7 @@ dependencies = [ requires-dist = [ { name = "google-genai", specifier = "==1.12.1" }, { name = "pre-commit", specifier = ">=4.5.1" }, + { name = "pytest", specifier = ">=9.0.2" }, { name = "python-dotenv", specifier = "==1.1.0" }, { name = "ruff", specifier = ">=0.15.1" }, ] @@ -118,6 +120,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -222,6 +233,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -231,6 +251,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + [[package]] name = "platformdirs" version = "4.9.1" @@ -240,6 +269,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/77/e8c95e95f1d4cdd88c90a96e31980df7e709e51059fac150046ad67fac63/platformdirs-4.9.1-py3-none-any.whl", hash = "sha256:61d8b967d34791c162d30d60737369cbbd77debad5b981c4bfda1842e71e0d66", size = 21307, upload-time = "2026-02-14T21:02:43.492Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pre-commit" version = "4.5.1" @@ -363,6 +401,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.0" From 3c9d65c49dfbff1463ac23334b4205f2836c46ec Mon Sep 17 00:00:00 2001 From: iamserda <12580399+iamserda@users.noreply.github.com> Date: Fri, 27 Feb 2026 02:30:11 -0500 Subject: [PATCH 6/6] feat: add get_file_content function with error handling and file size limit; include tests for various file scenarios and add lorem.txt for testing --@iamserda --- calculator/lorem.txt | 1 + config.py | 1 + functions/get_file_content.py | 43 +++++++++++++++++++++++++++++++++++ test_get_file_content.py | 21 +++++++++++++++++ tests/test_get_files_info.py | 1 + 5 files changed, 67 insertions(+) create mode 100644 calculator/lorem.txt create mode 100644 config.py create mode 100644 functions/get_file_content.py create mode 100644 test_get_file_content.py diff --git a/calculator/lorem.txt b/calculator/lorem.txt new file mode 100644 index 0000000..a0ad99f --- /dev/null +++ b/calculator/lorem.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. Ex sapien vitae pellentesque sem placerat in id. Tempus leo eu aenean sed diam urna tempor. Bibendum egestas iaculis massa nisl malesuada lacinia integer. Class aptent taciti sociosqu ad litora torquent per. Natoque penatibus et magnis dis parturient montes nascetur. Nulla molestie mattis scelerisque maximus eget fermentum odio. Mauris pharetra vestibulum fusce dictum risus blandit quis. Ante condimentum neque at luctus nibh finibus facilisis. Sollicitudin erat viverra ac tincidunt nam porta elementum. Commodo augue arcu dignissim velit aliquam imperdiet mollis. Cras eleifend turpis fames primis vulputate ornare sagittis. Proin libero feugiat tristique accumsan maecenas potenti ultricies. Curabitur facilisi cubilia curae hac habitasse platea dictumst. Adipiscing elit quisque faucibus ex sapien vitae pellentesque. Pretium tellus duis convallis tempus leo eu aenean. Fringilla lacus nec metus bibendum egestas iaculis massa. Ut hendrerit semper vel class aptent taciti sociosqu. Inceptos himenaeos orci varius natoque penatibus et magnis. Donec rhoncus eros lobortis nulla molestie mattis scelerisque. Purus est efficitur laoreet mauris pharetra vestibulum fusce. Nisi sodales consequat magna ante condimentum neque at. Interdum tortor ligula congue sollicitudin erat viverra ac. Euismod quam justo lectus commodo augue arcu dignissim. Porttitor ullamcorper rutrum gravida cras eleifend turpis fames. Dui felis venenatis ultrices proin libero feugiat tristique. Senectus netus suscipit auctor curabitur facilisi cubilia curae. Dolor sit amet consectetur adipiscing elit quisque faucibus. In id cursus mi pretium tellus duis convallis. Urna tempor pulvinar vivamus fringilla lacus nec metus. Lacinia integer nunc posuere ut hendrerit semper vel. Torquent per conubia nostra inceptos himenaeos orci varius. Montes nascetur ridiculus mus donec rhoncus eros lobortis. Fermentum odio phasellus non purus est efficitur laoreet. Blandit quis suspendisse aliquet nisi sodales consequat magna. Finibus facilisis dapibus etiam interdum tortor ligula congue. Porta elementum a enim euismod quam justo lectus. Imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida. Ornare sagittis vehicula praesent dui felis venenatis ultrices. Potenti ultricies habitant morbi senectus netus suscipit auctor. Platea dictumst lorem ipsum dolor sit amet consectetur. Vitae pellentesque sem placerat in id cursus mi. Eu aenean sed diam urna tempor pulvinar vivamus. Iaculis massa nisl malesuada lacinia integer nunc posuere. Taciti sociosqu ad litora torquent per conubia nostra. Et magnis dis parturient montes nascetur ridiculus mus. Mattis scelerisque maximus eget fermentum odio phasellus non. Vestibulum fusce dictum risus blandit quis suspendisse aliquet. Neque at luctus nibh finibus facilisis dapibus etiam. Viverra ac tincidunt nam porta elementum a enim. Arcu dignissim velit aliquam imperdiet mollis nullam volutpat. Turpis fames primis vulputate ornare sagittis vehicula praesent. Feugiat tristique accumsan maecenas potenti ultricies habitant morbi. Cubilia curae hac habitasse platea dictumst lorem ipsum. Quisque faucibus ex sapien vitae pellentesque sem placerat. Duis convallis tempus leo eu aenean sed diam. Nec metus bibendum egestas iaculis massa nisl malesuada. Semper vel class aptent taciti sociosqu ad litora. Orci varius natoque penatibus et magnis dis parturient. Eros lobortis nulla molestie mattis scelerisque maximus eget. Efficitur laoreet mauris pharetra vestibulum fusce dictum risus. Consequat magna ante condimentum neque at luctus nibh. Ligula congue sollicitudin erat viverra ac tincidunt nam. Justo lectus commodo augue arcu dignissim velit aliquam. Rutrum gravida cras eleifend turpis fames primis vulputate. Venenatis ultrices proin libero feugiat tristique accumsan maecenas. Suscipit auctor curabitur facilisi cubilia curae hac habitasse. Amet consectetur adipiscing elit quisque faucibus ex sapien. Cursus mi pretium tellus duis convallis tempus leo. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Nunc posuere ut hendrerit semper vel class aptent. Conubia nostra inceptos himenaeos orci varius natoque penatibus. Ridiculus mus donec rhoncus eros lobortis nulla molestie. Phasellus non purus est efficitur laoreet mauris pharetra. Suspendisse aliquet nisi sodales consequat magna ante condimentum. Dapibus etiam interdum tortor ligula congue sollicitudin erat. Aenim euismod quam justo lectus commodo augue. Nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend. Vehicula praesent dui felis venenatis ultrices proin libero. Habitant morbi senectus netus suscipit auctor curabitur facilisi. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. Ex sapien vitae pellentesque sem placerat in id. Tempus leo eu aenean sed diam urna tempor. Bibendum egestas iaculis massa nisl malesuada lacinia integer. Class aptent taciti sociosqu ad litora torquent per. Natoque penatibus et magnis dis parturient montes nascetur. Nulla molestie mattis scelerisque maximus eget fermentum odio. Mauris pharetra vestibulum fusce dictum risus blandit quis. Ante condimentum neque at luctus nibh finibus facilisis. Sollicitudin erat viverra ac tincidunt nam porta elementum. Commodo augue arcu dignissim velit aliquam imperdiet mollis. Cras eleifend turpis fames primis vulputate ornare sagittis. Proin libero feugiat tristique accumsan maecenas potenti ultricies. Curabitur facilisi cubilia curae hac habitasse platea dictumst. Adipiscing elit quisque faucibus ex sapien vitae pellentesque. Pretium tellus duis convallis tempus leo eu aenean. Fringilla lacus nec metus bibendum egestas iaculis massa. Ut hendrerit semper vel class aptent taciti sociosqu. Inceptos himenaeos orci varius natoque penatibus et magnis. Donec rhoncus eros lobortis nulla molestie mattis scelerisque. Purus est efficitur laoreet mauris pharetra vestibulum fusce. Nisi sodales consequat magna ante condimentum neque at. Interdum tortor ligula congue sollicitudin erat viverra ac. Euismod quam justo lectus commodo augue arcu dignissim. Porttitor ullamcorper rutrum gravida cras eleifend turpis fames. Dui felis venenatis ultrices proin libero feugiat tristique. Senectus netus suscipit auctor curabitur facilisi cubilia curae. Dolor sit amet consectetur adipiscing elit quisque faucibus. In id cursus mi pretium tellus duis convallis. Urna tempor pulvinar vivamus fringilla lacus nec metus. Lacinia integer nunc posuere ut hendrerit semper vel. Torquent per conubia nostra inceptos himenaeos orci varius. Montes nascetur ridiculus mus donec rhoncus eros lobortis. Fermentum odio phasellus non purus est efficitur laoreet. Blandit quis suspendisse aliquet nisi sodales consequat magna. Finibus facilisis dapibus etiam interdum tortor ligula congue. Porta elementum a enim euismod quam justo lectus. Imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida. Ornare sagittis vehicula praesent dui felis venenatis ultrices. Potenti ultricies habitant morbi senectus netus suscipit auctor. Platea dictumst lorem ipsum dolor sit amet consectetur. Vitae pellentesque sem placerat in id cursus mi. Eu aenean sed diam urna tempor pulvinar vivamus. Iaculis massa nisl malesuada lacinia integer nunc posuere. Taciti sociosqu ad litora torquent per conubia nostra. Et magnis dis parturient montes nascetur ridiculus mus. Mattis scelerisque maximus eget fermentum odio phasellus non. Vestibulum fusce dictum risus blandit quis suspendisse aliquet. Neque at luctus nibh finibus facilisis dapibus etiam. Viverra ac tincidunt nam porta elementum a enim. Arcu dignissim velit aliquam imperdiet mollis nullam volutpat. Turpis fames primis vulputate ornare sagittis vehicula praesent. Feugiat tristique accumsan maecenas potenti ultricies habitant morbi. Cubilia curae hac habitasse platea dictumst lorem ipsum. Quisque faucibus ex sapien vitae pellentesque sem placerat. Duis convallis tempus leo eu aenean sed diam. Nec metus bibendum egestas iaculis massa nisl malesuada. Semper vel class aptent taciti sociosqu ad litora. Orci varius natoque penatibus et magnis dis parturient. Eros lobortis nulla molestie mattis scelerisque maximus eget. Efficitur laoreet mauris pharetra vestibulum fusce dictum risus. Consequat magna ante condimentum neque at luctus nibh. Ligula congue sollicitudin erat viverra ac tincidunt nam. Justo lectus commodo augue arcu dignissim velit aliquam. Rutrum gravida cras eleifend turpis fames primis vulputate. Venenatis ultrices proin libero feugiat tristique accumsan maecenas. Suscipit auctor curabitur facilisi cubilia curae hac habitasse. Amet consectetur adipiscing elit quisque faucibus ex sapien. Cursus mi pretium tellus duis convallis tempus leo. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Nunc posuere ut hendrerit semper vel class aptent. Conubia nostra inceptos himenaeos orci varius natoque penatibus. Ridiculus mus donec rhoncus eros lobortis nulla molestie. Phasellus non purus est efficitur laoreet mauris pharetra. Suspendisse aliquet nisi sodales consequat magna ante condimentum. Dapibus etiam interdum tortor ligula congue sollicitudin erat. Aenim euismod quam justo lectus commodo augue. Nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend. Vehicula praesent dui felis venenatis ultrices proin libero. Habitant morbi senectus netus suscipit auctor curabitur facilisi. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. Lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus mus donec rhoncus eros lobortis nulla molestie mattis scelerisque maximus eget fermentum odio phasellus non purus est efficitur laoreet mauris pharetra vestibulum fusce dictum risus blandit quis suspendisse aliquet nisi sodales consequat magna ante condimentum neque at luctus nibh finibus facilisis dapibus etiam interdum tortor ligula congue sollicitudin erat viverra ac tincidunt nam porta elementum a enim euismod quam justo lectus commodo augue arcu dignissim velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend turpis fames primis vulputate ornare sagittis vehicula praesent dui felis venenatis ultrices proin libero feugiat tristique accumsan maecenas potenti ultricies habitant morbi senectus netus suscipit auctor curabitur facilisi cubilia curae hac habitasse platea dictumst lorem ipsum dolor sit amet consectetur adipiscing elit quisque faucibus ex sapien vitae pellentesque sem placerat in id cursus mi pretium tellus duis convallis tempus leo eu aenean sed diam urna tempor pulvinar vivamus fringilla lacus nec metus bibendum egestas iaculis massa nisl malesuada lacinia integer nunc posuere ut hendrerit semper vel class aptent taciti sociosqu ad litora torquent per conubia nostra inceptos himenaeos orci varius natoque penatibus et. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. Ex sapien vitae pellentesque sem placerat in id. Tempus leo eu aenean sed diam urna tempor. Bibendum egestas iaculis massa nisl malesuada lacinia integer. Class aptent taciti sociosqu ad litora torquent per. Natoque penatibus et magnis dis parturient montes nascetur. Nulla molestie mattis scelerisque maximus eget fermentum odio. Mauris pharetra vestibulum fusce dictum risus blandit quis. Ante condimentum neque at luctus nibh finibus facilisis. Sollicitudin erat viverra ac tincidunt nam porta elementum. Commodo augue arcu dignissim velit aliquam imperdiet mollis. Cras eleifend turpis fames primis vulputate ornare sagittis. Proin libero feugiat tristique accumsan maecenas potenti ultricies. Curabitur facilisi cubilia curae hac habitasse platea dictumst. Adipiscing elit quisque faucibus ex sapien vitae pellentesque. Pretium tellus duis convallis tempus leo eu aenean. Fringilla lacus nec metus bibendum egestas iaculis massa. Ut hendrerit semper vel class aptent taciti sociosqu. Inceptos himenaeos orci varius natoque penatibus et magnis. Donec rhoncus eros lobortis nulla molestie mattis scelerisque. Purus est efficitur laoreet mauris pharetra vestibulum fusce. Nisi sodales consequat magna ante condimentum neque at. Interdum tortor ligula congue sollicitudin erat viverra ac. Euismod quam justo lectus commodo augue arcu dignissim. Porttitor ullamcorper rutrum gravida cras eleifend turpis fames. Dui felis venenatis ultrices proin libero feugiat tristique. Senectus netus suscipit auctor curabitur facilisi cubilia curae. Dolor sit amet consectetur adipiscing elit quisque faucibus. In id cursus mi pretium tellus duis convallis. Urna tempor pulvinar vivamus fringilla lacus nec metus. Lacinia integer nunc posuere ut hendrerit semper vel. Torquent per conubia nostra inceptos himenaeos orci varius. Montes nascetur ridiculus mus donec rhoncus eros lobortis. Fermentum odio phasellus non purus est efficitur laoreet. Blandit quis suspendisse aliquet nisi sodales consequat magna. Finibus facilisis dapibus etiam interdum tortor ligula congue. Porta elementum a enim euismod quam justo lectus. Imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida. Ornare sagittis vehicula praesent dui felis venenatis ultrices. Potenti ultricies habitant morbi senectus netus suscipit auctor. Platea dictumst lorem ipsum dolor sit amet consectetur. Vitae pellentesque sem placerat in id cursus mi. Eu aenean sed diam urna tempor pulvinar vivamus. Iaculis massa nisl malesuada lacinia integer nunc posuere. Taciti sociosqu ad litora torquent per conubia nostra. Et magnis dis parturient montes nascetur ridiculus mus. Mattis scelerisque maximus eget fermentum odio phasellus non. Vestibulum fusce dictum risus blandit quis suspendisse aliquet. Neque at luctus nibh finibus facilisis dapibus etiam. Viverra ac tincidunt nam porta elementum a enim. Arcu dignissim velit aliquam imperdiet mollis nullam volutpat. Turpis fames primis vulputate ornare sagittis vehicula praesent. Feugiat tristique accumsan maecenas potenti ultricies habitant morbi. Cubilia curae hac habitasse platea dictumst lorem ipsum. Quisque faucibus ex sapien vitae pellentesque sem placerat. Duis convallis tempus leo eu aenean sed diam. Nec metus bibendum egestas iaculis massa nisl malesuada. Semper vel class aptent taciti sociosqu ad litora. Orci varius natoque penatibus et magnis dis parturient. Eros lobortis nulla molestie mattis scelerisque maximus eget. Efficitur laoreet mauris pharetra vestibulum fusce dictum risus. Consequat magna ante condimentum neque at luctus nibh. Ligula congue sollicitudin erat viverra ac tincidunt nam. Justo lectus commodo augue arcu dignissim velit aliquam. Rutrum gravida cras eleifend turpis fames primis vulputate. Venenatis ultrices proin libero feugiat tristique accumsan maecenas. Suscipit auctor curabitur facilisi cubilia curae hac habitasse. Amet consectetur adipiscing elit quisque faucibus ex sapien. Cursus mi pretium tellus duis convallis tempus leo. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Nunc posuere ut hendrerit semper vel class aptent. Conubia nostra inceptos himenaeos orci varius natoque penatibus. Ridiculus mus donec rhoncus eros lobortis nulla molestie. Phasellus non purus est efficitur laoreet mauris pharetra. Suspendisse aliquet nisi sodales consequat magna ante condimentum. Dapibus etiam interdum tortor ligula congue sollicitudin erat. Aenim euismod quam justo lectus commodo augue. Nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend. Vehicula praesent dui felis venenatis ultrices proin libero. Habitant morbi senectus netus suscipit auctor curabitur facilisi. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. Ex sapien vitae pellentesque sem placerat in id. Tempus leo eu aenean sed diam urna tempor. Bibendum egestas iaculis massa nisl malesuada lacinia integer. Class aptent taciti sociosqu ad litora torquent per. Natoque penatibus et magnis dis parturient montes nascetur. Nulla molestie mattis scelerisque maximus eget fermentum odio. Mauris pharetra vestibulum fusce dictum risus blandit quis. Ante condimentum neque at luctus nibh finibus facilisis. Sollicitudin erat viverra ac tincidunt nam porta elementum. Commodo augue arcu dignissim velit aliquam imperdiet mollis. Cras eleifend turpis fames primis vulputate ornare sagittis. Proin libero feugiat tristique accumsan maecenas potenti ultricies. Curabitur facilisi cubilia curae hac habitasse platea dictumst. Adipiscing elit quisque faucibus ex sapien vitae pellentesque. Pretium tellus duis convallis tempus leo eu aenean. Fringilla lacus nec metus bibendum egestas iaculis massa. Ut hendrerit semper vel class aptent taciti sociosqu. Inceptos himenaeos orci varius natoque penatibus et magnis. Donec rhoncus eros lobortis nulla molestie mattis scelerisque. Purus est efficitur laoreet mauris pharetra vestibulum fusce. Nisi sodales consequat magna ante condimentum neque at. Interdum tortor ligula congue sollicitudin erat viverra ac. Euismod quam justo lectus commodo augue arcu dignissim. Porttitor ullamcorper rutrum gravida cras eleifend turpis fames. Dui felis venenatis ultrices proin libero feugiat tristique. Senectus netus suscipit auctor curabitur facilisi cubilia curae. Dolor sit amet consectetur adipiscing elit quisque faucibus. In id cursus mi pretium tellus duis convallis. Urna tempor pulvinar vivamus fringilla lacus nec metus. Lacinia integer nunc posuere ut hendrerit semper vel. Torquent per conubia nostra inceptos himenaeos orci varius. Montes nascetur ridiculus mus donec rhoncus eros lobortis. Fermentum odio phasellus non purus est efficitur laoreet. Blandit quis suspendisse aliquet nisi sodales consequat magna. Finibus facilisis dapibus etiam interdum tortor ligula congue. Porta elementum a enim euismod quam justo lectus. Imperdiet mollis nullam volutpat porttitor ullamcorper rutrum gravida. Ornare sagittis vehicula praesent dui felis venenatis ultrices. Potenti ultricies habitant morbi senectus netus suscipit auctor. Platea dictumst lorem ipsum dolor sit amet consectetur. Vitae pellentesque sem placerat in id cursus mi. Eu aenean sed diam urna tempor pulvinar vivamus. Iaculis massa nisl malesuada lacinia integer nunc posuere. Taciti sociosqu ad litora torquent per conubia nostra. Et magnis dis parturient montes nascetur ridiculus mus. Mattis scelerisque maximus eget fermentum odio phasellus non. Vestibulum fusce dictum risus blandit quis suspendisse aliquet. Neque at luctus nibh finibus facilisis dapibus etiam. Viverra ac tincidunt nam porta elementum a enim. Arcu dignissim velit aliquam imperdiet mollis nullam volutpat. Turpis fames primis vulputate ornare sagittis vehicula praesent. Feugiat tristique accumsan maecenas potenti ultricies habitant morbi. Cubilia curae hac habitasse platea dictumst lorem ipsum. Quisque faucibus ex sapien vitae pellentesque sem placerat. Duis convallis tempus leo eu aenean sed diam. Nec metus bibendum egestas iaculis massa nisl malesuada. Semper vel class aptent taciti sociosqu ad litora. Orci varius natoque penatibus et magnis dis parturient. Eros lobortis nulla molestie mattis scelerisque maximus eget. Efficitur laoreet mauris pharetra vestibulum fusce dictum risus. Consequat magna ante condimentum neque at luctus nibh. Ligula congue sollicitudin erat viverra ac tincidunt nam. Justo lectus commodo augue arcu dignissim velit aliquam. Rutrum gravida cras eleifend turpis fames primis vulputate. Venenatis ultrices proin libero feugiat tristique accumsan maecenas. Suscipit auctor curabitur facilisi cubilia curae hac habitasse. Amet consectetur adipiscing elit quisque faucibus ex sapien. Cursus mi pretium tellus duis convallis tempus leo. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Nunc posuere ut hendrerit semper vel class aptent. Conubia nostra inceptos himenaeos orci varius natoque penatibus. Ridiculus mus donec rhoncus eros lobortis nulla molestie. Phasellus non purus est efficitur laoreet mauris pharetra. Suspendisse aliquet nisi sodales consequat magna ante condimentum. Dapibus etiam interdum tortor ligula congue sollicitudin erat. Aenim euismod quam justo lectus commodo augue. Nullam volutpat porttitor ullamcorper rutrum gravida cras eleifend. Vehicula praesent dui felis venenatis ultrices proin libero. Habitant morbi senectus netus suscipit auctor curabitur facilisi. Lorem ipsum dolor sit amet consectetur adipiscing elit. Sem placerat in id cursus mi pretium tellus. Sed diam urna tempor pulvinar vivamus fringilla lacus. Nisl malesuada lacinia integer nunc posuere ut hendrerit. Ad litora torquent per conubia nostra inceptos himenaeos. Dis parturient montes nascetur ridiculus mus donec rhoncus. Maximus eget fermentum odio phasellus non purus est. Dictum risus blandit quis suspendisse aliquet nisi sodales. Luctus nibh finibus facilisis dapibus etiam interdum tortor. Tincidunt nam porta elementum a enim euismod quam. Velit aliquam imperdiet mollis nullam volutpat porttitor ullamcorper. Primis vulputate ornare sagittis vehicula praesent dui felis. Accumsan maecenas potenti ultricies habitant morbi senectus netus. Hac habitasse platea dictumst lorem ipsum dolor sit. diff --git a/config.py b/config.py new file mode 100644 index 0000000..7b807a0 --- /dev/null +++ b/config.py @@ -0,0 +1 @@ +MAX_CHARS = 10_000 diff --git a/functions/get_file_content.py b/functions/get_file_content.py new file mode 100644 index 0000000..d74ffe1 --- /dev/null +++ b/functions/get_file_content.py @@ -0,0 +1,43 @@ +import os +from pathlib import Path +from config import MAX_CHARS + + +def get_file_content( + working_directory: str | None = None, file_path: str | None = None +): + try: + if working_directory is None: + return f'Error: "{working_directory}" cannot be None' + if file_path is None: + return f'Error: "The file path {file_path}" must be valid.' + + abs_work_dir = Path.absolute(Path(working_directory)) + abs_file_path = Path.absolute(abs_work_dir.joinpath(file_path)) + file_is_in_working_dir = os.path.commonpath( + [str(abs_file_path), str(abs_work_dir)] + ) == str(abs_work_dir) + + if not file_is_in_working_dir: + return f'Error: Cannot read "{file_path}" as it is outside the permitted working directory' + + if not os.path.isfile(abs_file_path): + return f'Error: File not found or is not a regular file: "{abs_file_path}"' + + normalized_filepath = os.path.normpath(str(abs_file_path)) + file_size = os.path.getsize(normalized_filepath) + with open(file=normalized_filepath) as file: + content = file.read(MAX_CHARS) + if file_size > MAX_CHARS: + content += ( + f'[...File "{file_path}" truncated at {MAX_CHARS} characters]' + ) + return content + except Exception as exp: + return f"Error: {exp}!" + + +if __name__ == "__main__": + # ans = get_file_content("./calculator", "./calculator/pkg/calculator.py") + ans = get_file_content("./calculator", "lorem.txt") + print(ans) diff --git a/test_get_file_content.py b/test_get_file_content.py new file mode 100644 index 0000000..6a50398 --- /dev/null +++ b/test_get_file_content.py @@ -0,0 +1,21 @@ +from functions.get_file_content import get_file_content +from config import MAX_CHARS + + +if __name__ == "__main__": + trucated_msg = 'File "lorem.txt" truncated at 10000 characters]' + actual_result = get_file_content("calculator", "lorem.txt") + assert len(actual_result) > MAX_CHARS + assert actual_result[-len(trucated_msg) :] == trucated_msg + + actual_result = get_file_content("calculator", "main.py") + print("\n", actual_result) + + actual_result = get_file_content("calculator", "pkg/calculator.py") + print("\n", actual_result) + + actual_result = get_file_content("calculator", "/bin/cat") + print(actual_result) + + actual_result = get_file_content("calculator", "pkg/does_not_exist.py") + print(actual_result) diff --git a/tests/test_get_files_info.py b/tests/test_get_files_info.py index 0d55a01..35c6789 100644 --- a/tests/test_get_files_info.py +++ b/tests/test_get_files_info.py @@ -5,6 +5,7 @@ class TestGetFilesInfo: def test_known_valid_path1(self): expected_answer = """Result for current directory: - __init__.py: file_size: 0, is_dir=False + - lorem.txt: file_size: 37831, is_dir=False - tests.py: file_size: 1354, is_dir=False - main.py: file_size: 741, is_dir=False - pkg: file_size: 128, is_dir=True