From 8a7c44b20ad3a259583abbc040ca0e959125c434 Mon Sep 17 00:00:00 2001 From: Ritchie Mwewa <74001397+rly0nheart@users.noreply.github.com> Date: Mon, 12 May 2025 11:19:51 +0200 Subject: [PATCH] Drop dependency (whats-that-code). Return results as SimpleNamespace object(s) --- README.md | 26 ++++++++++------- poetry.lock | 61 +--------------------------------------- pyproject.toml | 3 +- searchcode/__init__.py | 2 +- searchcode/api.py | 48 ++++++++++++++++++++++++------- searchcode/cli.py | 43 +++++++++++++--------------- tests/test_searchcode.py | 14 +++++---- 7 files changed, 86 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 0f61741..db12c18 100644 --- a/README.md +++ b/README.md @@ -91,13 +91,14 @@ searchcode "import module" --languages java,javascript #### In Code ```python +from pprint import pprint from searchcode import Searchcode sc = Searchcode(user_agent="My-Searchcode-script") search = sc.search(query="import module", languages=["Java", "JavaScript"]) -for result in search.get("results"): - print(result.language) +for result in search.results: + pprint(result.language) ``` ___ @@ -113,13 +114,14 @@ searchcode "import module" --sources bitbucket,codeplex #### In Code ```python +from pprint import pprint from searchcode import Searchcode sc = Searchcode(user_agent="My-Searchcode-script") search = sc.search(query="import module", sources=["BitBucket", "CodePlex"]) -for result in search.get("results"): - print(result.filename) +for result in search.results: + pprint(result.filename) ``` ___ @@ -135,14 +137,14 @@ searchcode "import module" --lines-of-code-gt 500 --lines-of-code-lt 1000 #### In Code ```python - +from pprint import pprint from searchcode import Searchcode sc = Searchcode(user_agent="My-Searchcode-script") search = sc.search(query="import module", lines_of_code_gt=500, lines_of_code_lt=1000) -for result in search.get("results"): - print(result) +for result in search.results: + pprint(result) ``` ___ @@ -158,11 +160,13 @@ searchcode "import module" --callback myCallback #### In Code ```python +from pprint import pprint from searchcode import Searchcode sc = Searchcode(user_agent="My-Searchcode-script") search = sc.search(query="import module", callback="myCallback") -print(search) + +pprint(search) ``` ` @@ -221,8 +225,10 @@ searchode code 4061576 from searchcode import Searchcode sc = Searchcode(user_agent="My-Searchcode-script") -code = sc.code(4061576) -print(code) +data = sc.code(4061576) + +print(data.language) +print(data.code) ``` --- diff --git a/poetry.lock b/poetry.lock index 296ee98..f3e654b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -142,18 +142,6 @@ files = [ ] markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} -[[package]] -name = "defusedxml" -version = "0.7.1" -description = "XML bomb protection for Python stdlib modules" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -330,21 +318,6 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pyrankvote" -version = "2.0.6" -description = "PyRankVote is a python library for different ranked voting methods, like instant-runoff voting, single transferable vote and preferential block voting, created by Jon Tingvold." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pyrankvote-2.0.6-py3-none-any.whl", hash = "sha256:9199080b842d9885f948623a7bfab9c2245c544ed0eb711189e5e2021a38f19c"}, - {file = "pyrankvote-2.0.6.tar.gz", hash = "sha256:93b39a0f010d8647bc60a94d2136271cc6979b626a2607ad185368fc505fa142"}, -] - -[package.dependencies] -tabulate = "*" - [[package]] name = "pytest" version = "8.3.5" @@ -431,21 +404,6 @@ typing_extensions = ">=4" dev = ["mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "rich-codex", "ruff", "types-setuptools"] docs = ["markdown_include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"] -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - [[package]] name = "tomli" version = "2.2.1" @@ -519,24 +477,7 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "whats-that-code" -version = "0.2.0" -description = "Guess programming language from a string or file." -optional = false -python-versions = ">=3.6,<4.0" -groups = ["main"] -files = [ - {file = "whats_that_code-0.2.0-py3-none-any.whl", hash = "sha256:923fb3d84ad27c265da7ac2b12251a2055c3325c0bd4dae5e527085b99e84273"}, - {file = "whats_that_code-0.2.0.tar.gz", hash = "sha256:938fb2443a6a7eb23ceee20f0c246922f206c7356b542113d3161314f8cdc61d"}, -] - -[package.dependencies] -defusedxml = "*" -pygments = "*" -pyrankvote = "*" - [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "a30d58e9246c747391689d47907d41d7dee51d748b95178e29c60665447e8906" +content-hash = "5d34933ba1711c5251ba0df596fccc4ef7ac1d4e2b221dd3239c7deb7cc15b4b" diff --git a/pyproject.toml b/pyproject.toml index f0ca4db..5f90543 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "searchcode" -version = "0.4.4" +version = "0.5.0" description = "Simple, comprehensive code search." authors = ["Ritchie Mwewa "] license = "GPLv3+" @@ -20,7 +20,6 @@ classifiers = [ python = "^3.10" requests = "^2.32.2" rich-click = "^1.8.8" -whats-that-code = "^0.2.0" [tool.poetry.group.dev.dependencies] flake8 = "^7.1.2" diff --git a/searchcode/__init__.py b/searchcode/__init__.py index d4c7500..483e5ac 100644 --- a/searchcode/__init__.py +++ b/searchcode/__init__.py @@ -20,7 +20,7 @@ from .api import Searchcode __pkg__ = "searchcode" -__version__ = "0.4.4" +__version__ = "0.5.0" __author__ = "Ritchie Mwewa" diff --git a/searchcode/api.py b/searchcode/api.py index becda3b..b476dca 100644 --- a/searchcode/api.py +++ b/searchcode/api.py @@ -17,19 +17,19 @@ import typing as t from platform import python_version, platform +from types import SimpleNamespace import requests from .filters import CODE_LANGUAGES, CODE_SOURCES, get_language_ids, get_source_ids -BASE_API_ENDPOINT = "https://searchcode.com/api" - __all__ = ["Searchcode"] class Searchcode: def __init__(self, user_agent: str): self.user_agent = user_agent + self.__base_api_endpoint: str = "https://searchcode.com/api" def search( self, @@ -41,7 +41,7 @@ def search( lines_of_code_gt: t.Optional[int] = None, lines_of_code_lt: t.Optional[int] = None, callback: t.Optional[str] = None, - ) -> t.Union[t.Dict, str]: + ) -> t.Union[SimpleNamespace, str]: """ Searches and returns code snippets matching the query. @@ -80,7 +80,7 @@ def search( source_ids = [] if not sources else get_source_ids(source_names=sources) response = self.__send_request( - endpoint=f"{BASE_API_ENDPOINT}/{'jsonp_codesearch_I' if callback else 'codesearch_I'}/", + endpoint=f"{self.__base_api_endpoint}/{'jsonp_codesearch_I' if callback else 'codesearch_I'}/", params=[ ("q", query), ("p", page), @@ -95,22 +95,25 @@ def search( ) if not callback: - response["results"] = response.get("results", [])[:per_page] + response = self.__to_namespace_obj(response=response) + response.results = response.results[:per_page] return response - def code(self, __id: int) -> str: + def code(self, __id: int) -> SimpleNamespace: """ Returns the raw data from a code file given the code ID which can be found as the `id` in a code search result. :param __id: The unique identifier of the code result. :type __id: int - :return: Raw code result data. - :rtype: str + :return: SimpleNamespace object containing code file data. + :rtype: SimpleNamespace """ - response = self.__send_request(endpoint=f"{BASE_API_ENDPOINT}/result/{__id}") - return response.get("code") + response = self.__send_request( + endpoint=f"{self.__base_api_endpoint}/result/{__id}" + ) + return self.__to_namespace_obj(response=response) # This is deprecated (for now). # def related(_id: int) -> Dict: @@ -156,3 +159,28 @@ def __send_request( ) response.raise_for_status() return response.text if callback else response.json() + + def __to_namespace_obj( + self, + response: t.Union[t.List[t.Dict], t.Dict], + ) -> t.Union[t.List[SimpleNamespace], SimpleNamespace, t.List[t.Dict], t.Dict]: + """ + Recursively converts the API response into a SimpleNamespace object(s). + + :param response: The object to convert, either a dictionary or a list of dictionaries. + :type response: Union[List[Dict], Dict] + :return: A SimpleNamespace object or list of SimpleNamespace objects. + :rtype: Union[List[SimpleNamespace], SimpleNamespace, None] + """ + + if isinstance(response, t.Dict): + return SimpleNamespace( + **{ + key: self.__to_namespace_obj(response=value) + for key, value in response.items() + } + ) + elif isinstance(response, t.List): + return [self.__to_namespace_obj(response=item) for item in response] + else: + return response diff --git a/searchcode/cli.py b/searchcode/cli.py index cd32c17..377502c 100644 --- a/searchcode/cli.py +++ b/searchcode/cli.py @@ -18,6 +18,7 @@ import os import subprocess import typing as t +from types import SimpleNamespace import rich_click as click from rich import box @@ -25,7 +26,6 @@ from rich.panel import Panel from rich.pretty import pprint from rich.syntax import Syntax -from whats_that_code.election import guess_language_all_methods from . import License, __pkg__, __version__ from .api import Searchcode @@ -136,11 +136,13 @@ def search( __clear_screen() __update_window_title(text=query) - with console.status(f"Querying code index with [green]{query}[/]"): + with console.status( + f"Querying code index with search string: [green]{query}[/]..." + ): languages = languages.split(",") if languages else None sources = sources.split(",") if sources else None - results = sc.search( + response = sc.search( query=query, page=page, per_page=per_page, @@ -152,13 +154,9 @@ def search( ) ( - __print_jsonp(jsonp=results) + __print_jsonp(jsonp=response) if callback - else ( - pprint(results) - if pretty - else __print_panels(data=results.get("results")) - ) + else (pprint(response) if pretty else __print_panels(data=response.results)) ) @@ -172,13 +170,13 @@ def code(id: int): """ __clear_screen() __update_window_title(text=str(id)) - with console.status(f"Retrieving code data for [cyan]{id}[/]") as status: - code_data = sc.code(id) - if code_data: - status.update("Determining code language") - language = guess_language_all_methods(code=code_data) + with console.status(f"Fetching data for code file with ID: [cyan]{id}[/]..."): + data = sc.code(id) + lines = data.code + language = data.language + if lines: syntax = Syntax( - code=code_data, lexer=language, line_numbers=True, theme="dracula" + code=lines, lexer=language, line_numbers=True, theme="dracula" ) console.print(syntax) @@ -193,7 +191,7 @@ def __print_jsonp(jsonp: str) -> None: console.print(syntax) -def __print_panels(data: t.List[t.Dict]): +def __print_panels(data: t.List[SimpleNamespace]): """ Render a list of code records as rich panels with syntax highlighting. Line numbers are preserved and displayed alongside code content. @@ -218,14 +216,13 @@ def extract_code_string_with_linenumbers(lines_dict: t.Dict[str, str]) -> str: return "\n".join(numbered_lines) for item in data: - filename = item.get("filename", "Unknown") - repo = item.get("repo", "Unknown") - language = item.get("language", "text") - lines_count = item.get("linescount", "??") + filename = item.filename + repo = item.repo + language = item.language + lines_count = item.linescount + lines = item.lines - code_string = extract_code_string_with_linenumbers( - lines_dict=item.get("lines", {}) - ) + code_string = extract_code_string_with_linenumbers(lines_dict=lines.__dict__) syntax = Syntax( code=code_string, diff --git a/tests/test_searchcode.py b/tests/test_searchcode.py index 0e76424..b531ff3 100644 --- a/tests/test_searchcode.py +++ b/tests/test_searchcode.py @@ -22,14 +22,18 @@ def test_filter_by_extension(): search = sc.search("gsub ext:erb") - for result in search.get("results"): - assert result.get("filename").endswith(".erb") + for result in search.results: + assert result.filename.endswith(".erb") def test_code_result(): - code = sc.code(4061576) - assert isinstance(code, str) - assert "This file is part of Quake III Arena source code" in code + data = sc.code(4061576) + lines = data.code + language = data.language + + assert isinstance(lines, str) + assert "This file is part of Quake III Arena source code" in lines + assert language == "C" # deprecated (for now)