diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7579d97d..e518871d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,6 +13,7 @@ jobs: python-version: 3.11 - run: | pip install poetry + poetry version ${GITHUB_REF##*/v} poetry build - uses: actions/upload-artifact@v3 with: @@ -31,4 +32,4 @@ jobs: - name: Publish Package Distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - packages_dir: artifact/ + packages_dir: artifact/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..50a37d9d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# configuring pre-commit hooks for chasten. +# this ensures users do not commit problematic code +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-ast + files: '\.py$' + - id: check-case-conflict + - id: check-merge-conflict + - id: forbid-submodules + - id: trailing-whitespace + +- repo: local + hooks: + - id: fix-linting + files: '\.py$' + entry: poetry run task lint --fix + language: system + name: Fix Linting diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53df7809..e1a2ebaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,21 @@ If the issue already exists, share any helpful information that you have gained Any changes being made to our program must first be done in a branch or fork. Once you (and your fellow contributors) are done making changes, you may create a pull request (PR) to the [main repository](https://github.com/AstuteSource/chasten). +### Pre-Commit Hooks + +Enabling pre-commit hooks on your system will save you from accidentally making +unwanted changes, such as incorrect formatting, committing merge conflicts, etc. + +You can enable pre-commit hooks after installing `poetry`: + +```sh +poetry install +poetry run task pre-commit-install # this sets up pre-commit hooks +``` + +Now, when you make commits, our specific checks will run. This will give you +greater confidence that your contributions align with our standards! + ### Branches Branches are one of the possible features of GitHub that you could use to make changes to our project. Learn about branches [here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches). diff --git a/README.md b/README.md index 4c6a5921..276a3fa8 100644 --- a/README.md +++ b/README.md @@ -89,24 +89,6 @@ Follow these steps to install the `chasten` program: - Type `pipx list` and confirm that Chasten is installed - Type `chasten --help` to learn how to use the tool -## ๐Ÿ‹ Docker - -There is also the option to use [Docker](https://www.docker.com/) to use `chasten` - -Follow these steps to utilize Docker: - -- Install [Docker Desktop](https://docs.docker.com/get-docker/) for your operating system -- Ensure Docker Desktop is running -- `cd` into the chasten directory where the `Dockerfile` is located -- Type `docker build -t chasten .` to build the container -- Type one of the following commands to run the container: - - Windows (Command Prompt) -> `docker run --rm -v "%cd%":/root/src -it chasten` - - Windows (Powershell) -> `docker run --rm -v ${pwd}:/root/src -it chasten` - - Mac/Ubuntu -> `docker run --rm -v $(pwd):/root/src -it chasten` -- Inside the container type `poetry install` -- Outside of the container type `docker ps` to view running container information -- Outside of the container type `docker commit ` to save the dependecy installation -- Now you can use Docker for all of your `chasten` needs! ## ๐Ÿช‚ Configuration @@ -345,8 +327,67 @@ class DebugLevel(str, Enum): CRITICAL = "CRITICAL" ``` +## โœจ chasten --help + +```shell + Usage: chasten [OPTIONS] COMMAND [ARGS]... + +โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ --install-completion Install completion for the current shell. โ”‚ +โ”‚ --show-completion Show completion for the current shell, to copy it or โ”‚ +โ”‚ customize the installation. โ”‚ +โ”‚ --help Show this message and exit. โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€ Commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ analyze ๐Ÿ’ซ Analyze the AST of Python source code. โ”‚ +โ”‚ configure ๐Ÿช‚ Manage chasten's configuration. โ”‚ +โ”‚ datasette-publish ๐ŸŒŽ Publish a datasette to Fly or Vercel. โ”‚ +โ”‚ datasette-serve ๐Ÿƒ Start a local datasette server. โ”‚ +โ”‚ integrate ๐Ÿšง Integrate files and make a database. โ”‚ +โ”‚ interact ๐Ÿš€ Interactively configure and run. โ”‚ +โ”‚ log ๐Ÿฆš Start the logging server. โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +``` + +## ๐Ÿง‘โ€๐Ÿ’ป Development Enviroment +### ๐Ÿ  Local +Follow these steps to install the `chasten` tool for future development: + +- The development and use of Chasten requires [Python 3.11](https://www.python.org/downloads/release/python-3115/), must be greater or equal to version 3.11.5. +- The developers of Chasten use [Poetry](https://github.com/python-poetry/poetry) for packaging and dependency management. + +Once Python and Poetry is installed, please go to the [Chasten](https://github.com/AstuteSource/chasten) repository on github and install the tool using the `git clone` command in your terminal. Then navigate to the Chasten directory and run the command `poetry install` to install all the dependencies. + +### ๐Ÿ‹ Docker + +There is also the option to use [Docker](https://www.docker.com/) to use `chasten` + +Follow these steps to utilize Docker: + +- Install [Docker Desktop](https://docs.docker.com/get-docker/) for your operating system +- Ensure Docker Desktop is running +- `cd` into the chasten directory where the `Dockerfile` is located +- Type `docker build -t chasten .` to build the container +- Type one of the following commands to run the container: + - Windows (Command Prompt) -> `docker run --rm -v "%cd%":/root/src -it chasten` + - Windows (Powershell) -> `docker run --rm -v ${pwd}:/root/src -it chasten` + - Mac/Ubuntu -> `docker run --rm -v $(pwd):/root/src -it chasten` +- Inside the container type `poetry install` +- Outside of the container type `docker ps` to view running container information +- Outside of the container type `docker commit ` to save the dependecy installation +- Now you can use Docker for all of your `chasten` needs! + +## ๐Ÿ“‹ Development Tasks + +- **Linting and Formatting** + - We use the linting tools `Black` and `Ruff` on Chasten to ensure code consistency, readability, and adherence to predefined formatting standards across the entire project, ultimately enhancing maintainability and collaboration among developers. + - Please ensure all content in the project follow the appropriate format by running the following commands: `poetry run task fiximports` and/or `poetry run task fixformat` before shipping new features. If features are shipped with linting issues, the build will break on github due to the failure of the test suite. +- **Testing and Coverage** + - Chasten uses the testing tools `Pytest` and `Hypothesis` which enables us to fortify code consistency, readability, and alignment with established formatting standards throughout the project. When writing test cases for features, create a new file in the tests directory with the naming convention `test_(name of file)`. + - Please ensure all content in the project passes the tests by running the following commands: `poetry run task test` for most cases or if you would like to test the OpenAI API based features `poetry run task test-api` before shipping. If features are shipped without a test suite, the coverage will be lowered on github due to the addition of untested code and may potenitally lead to larger issues in the future. ## ๐Ÿค— Learning @@ -374,6 +415,47 @@ class DebugLevel(str, Enum): - [Python Treesitter](https://github.com/tree-sitter/py-tree-sitter) offers a Python language bindings for to parsing and querying with Treesitter +## ๐Ÿค“ Chasten vs. Symbex + +Chasten and Symbex, which was created by Simon Willison, are both tools designed for analyzing Python source code, particularly focusing on searching for functions and classes within files. While they share a common goal, there are notable differences between the two, especially in terms of their command-line interfaces and functionality. + +In terms of Command-Line Interface, Symbex employs a concise CLI, utilizing abbreviations for various options. For instance, the command to search for function signatures in a file named `test_debug.py` is as follows: + +```python +command :symbex -s -f symbex/test_debug.py + def test_debug_level_values(): + def test_debug_level_isinstance(): + def test_debug_level_iteration(): + def test_debug_destination_values(): + def test_debug_destination_isinstance(): + def test_debug_destination_iteration(): + def test_level_destination_invalid(): + def test_debug_destination_invalid(): +``` + +Chasten, on the other hand, leverages Python packages such as Typer and Rich to provide a user-friendly and feature-rich command-line interface. The available commands for Chasten include: + +- analyze ๐Ÿ’ซ Analyze the AST of Python source code +- configure ๐Ÿช‚ Manage chasten's configuration +- datasette-publish ๐ŸŒŽ Publish a datasette to Fly or Vercel +- datasette-serve ๐Ÿƒ Start a local datasette server +- integrate ๐Ÿšง Integrate files and make a database +- interact ๐Ÿš€ Interactively configure and run +- log ๐Ÿฆš Start the logging server. + +In terms of functionality, Symbex is designed to search Python code for functions and classes by name or wildcard. It provides the ability to filter results based on various criteria, including function type (async or non-async), documentation presence, visibility, and type annotations. + +On the other hand, Chasten's `analyze` command performs AST analysis on Python source code. It allows users to specify a project name, XPATH version, search path, and various filtering criteria. Chasten supports checks for inclusion and exclusion based on attributes, values, and match confidence levels. The tool also provides extensive configuration options and the ability to save results in different formats, including markdown. + +In summary, while both Chasten and Symbex serve the common purpose of analyzing Python source code, Chasten offers a more versatile and user-friendly CLI with additional features of configuration and result management. Symbex, on the other hand, adopts a concise CLI with a focus on searching and filtering functionalities. The choice between the two tools depends on the user's preferences and specific requirements for Python code analysis. + +## ๐Ÿ“ฆ Similar Tools + +In addition to Chasten and Symbex, several other tools offer unique capabilities for analyzing and searching through Python source code, each catering to specific use cases. + +- [pyastgrep](https://github.com/spookylukey/pyastgrep) is a tool developed by Luke Plant that provides advanced capabilities for viewing and searching AST using XPath expressions. It allows users to define complex patterns and queries to navigate and extract information from Python code, making it a powerful tool for in-depth code analysis. +- [treesitter](https://tree-sitter.github.io/tree-sitter/) offers a generic and efficient approach to parsing source code and building AST. It supports multiple languages, providing a consistent API for interacting with parsed code across different language ecosystems. + ## ๐Ÿง—Improvement - Found a bug or have a feature that the development team should implement? diff --git a/chasten/configApp.py b/chasten/configApp.py index 2177f785..60931957 100644 --- a/chasten/configApp.py +++ b/chasten/configApp.py @@ -111,7 +111,7 @@ class config_App(App): color: black; } """ - Check: ClassVar = ["", "1", False] # noqa: RUF012 + Check: ClassVar = ["", "1", False] Valid: bool = False def on_input_changed(self, event: Input.Changed) -> None: diff --git a/chasten/configuration.py b/chasten/configuration.py index d3c79033..e9f9e222 100644 --- a/chasten/configuration.py +++ b/chasten/configuration.py @@ -14,13 +14,7 @@ from rich.traceback import install from urllib3.util import Url, parse_url -from chasten import ( - constants, - filesystem, - output, - util, - validate, -) +from chasten import constants, filesystem, output, util, validate def configure_tracebacks() -> None: diff --git a/chasten/main.py b/chasten/main.py index 309e462c..c5e38c08 100644 --- a/chasten/main.py +++ b/chasten/main.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import Dict, List, Tuple, Union +import pyastgrep # type: ignore import typer from pyastgrep import search as pyastgrepsearch # type: ignore @@ -248,7 +249,7 @@ def configure( # noqa: PLR0913 @cli.command() -def analyze( # noqa: PLR0912, PLR0913, PLR0915 +def analyze( # noqa: PLR0912, PLR0913, PLR0915 project: str = typer.Argument(help="Name of the project."), xpath: Path = typer.Option( str, @@ -291,6 +292,18 @@ def analyze( # noqa: PLR0912, PLR0913, PLR0915 writable=True, resolve_path=True, ), + view_XML: str = typer.Option( + None, + "--view-xml", + "-v", + help="Prints and saves the xml representation of the input file(s)", + ), + save_XML: str = typer.Option( + None, + "--save-xml", + "-sx", + help="Saves the xml representation of the input file(s)", + ), store_result: Path = typer.Option( None, "--markdown-storage", @@ -628,7 +641,65 @@ def analyze( # noqa: PLR0912, PLR0913, PLR0915 ) # output the name of the saved file if saving successfully took place if saved_file_name: - output.console.print(f":sparkles: Saved the file '{saved_file_name}'") + output.console.print(f"\n:sparkles: Saved the file '{saved_file_name}'") + # --save-xml and --view-xml + if save_XML is not None or view_XML is not None: + output.console.print(":memo: Saving XML...") + try: + if os.path.isdir(input_path): + for each_file in os.listdir(input_path): + each_file = Path(input_path) / Path(each_file) # type: ignore # noqa: PLW2901 + if ( + not os.path.isdir(each_file) + and os.path.isfile(each_file) + and str(each_file).endswith(".py") + ): + # Read the bytes of the input path and store them in the 'contents' variable + contents = Path(each_file).read_bytes() + # Use pyastgrep to parse the contents of the Python file at 'input_path' + _, ast = pyastgrep.files.parse_python_file( + contents, each_file, auto_dedent=False + ) + # Convert the Abstract Syntax Tree (AST) into an XML representation + xml_root = pyastgrep.asts.ast_to_xml(ast, {}) + # Check if view_xml is chosen + if view_XML is not None: + output.console.print( + pyastgrep.xml.tostring( + xml_root, pretty_print=True + ).decode("utf-8") + ) + elif os.path.isdir(each_file): + for sub_file in os.listdir(each_file): + sub_file = Path(each_file) / Path(sub_file) # type: ignore # noqa: PLW2901 + if str(sub_file).endswith(".py"): + contents = Path(sub_file).read_bytes() + _, ast = pyastgrep.files.parse_python_file( + contents, sub_file, auto_dedent=False + ) + xml_root = pyastgrep.asts.ast_to_xml(ast, {}) + # Check if view_xml is chosen + if view_XML is not None: + output.console.print( + pyastgrep.xml.tostring( + xml_root, pretty_print=True + ).decode("utf-8") + ) + elif os.path.isfile(input_path) and str(input_path).endswith(".py"): + contents = Path(input_path).read_bytes() + _, ast = pyastgrep.files.parse_python_file( + contents, input_path, auto_dedent=False + ) + xml_root = pyastgrep.asts.ast_to_xml(ast, {}) + # Check if view_xml is chosen + if view_XML is not None: + output.console.print( + pyastgrep.xml.tostring(xml_root, pretty_print=True).decode( + "utf-8" + ) + ) + except FileNotFoundError: + output.console.print(":sweat: Sorry, could not convert to xml.") # confirm whether or not all of the checks passed # and then display the appropriate diagnostic message all_checks_passed = all(check_status_list) diff --git a/chasten/output.py b/chasten/output.py index bfa8bb0d..f0b1e64c 100644 --- a/chasten/output.py +++ b/chasten/output.py @@ -39,7 +39,7 @@ def setup( def print_diagnostics(verbose: bool, **configurations: Any) -> None: """Display all variables input to the function.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 # display diagnostic information for each configuration keyword argument if verbose: console.print(":sparkles: Configured with these parameters:") @@ -53,7 +53,7 @@ def print_diagnostics(verbose: bool, **configurations: Any) -> None: def opt_print_log(verbose: bool, **contents: Any) -> None: """Produce logging information and only print when not verbose.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 # iterate through each of the configuration keyword arguments for current in contents: # print the name and the value of the keyword argument @@ -66,7 +66,7 @@ def opt_print_log(verbose: bool, **contents: Any) -> None: def print_header() -> None: """Display tool details in the header.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 console.print() console.print( constants.chasten.Emoji + constants.markers.Space + constants.chasten.Tagline @@ -76,21 +76,21 @@ def print_header() -> None: def print_server() -> None: """Display server details in the header.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 console.print(constants.output.Syslog) console.print() def print_test_start() -> None: """Display details about the test run.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 console.print(constants.output.Test_Start) console.print() def print_test_finish() -> None: """Display details about the test run.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 console.print() console.print(":sparkles: Finished running test suite for the specified program") console.print() @@ -98,7 +98,7 @@ def print_test_finish() -> None: def print_footer() -> None: """Display concluding details in the footer.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 console.print() @@ -136,7 +136,7 @@ def shorten_file_name(file_name: str, max_length: int) -> str: def print_list_contents(container: List[Path]) -> None: """Display the contents of the list in an easy-to-read fashion.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 # group all of the files by the directory that contains them; # note that this is important because the contain can contain # paths that specify files in different directories @@ -159,7 +159,7 @@ def print_list_contents(container: List[Path]) -> None: def print_analysis_details(chasten: results.Chasten, verbose: bool = False) -> None: """Print all of the verbose debugging details for the results of an analysis.""" - global console # noqa: disable=PLW0603 + global console # noqa: PLW0602 # 1) Note: see the BaseModel definitions in results.py for more details # about the objects and their relationships # 2) Note: the _match object that is inside of a Match BaseModel subclass diff --git a/chasten/server.py b/chasten/server.py index acf75116..af3fe246 100644 --- a/chasten/server.py +++ b/chasten/server.py @@ -18,7 +18,7 @@ class SyslogUDPHandler(socketserver.BaseRequestHandler): def handle(self): """Receive a message and then display it in output and log it to a file.""" - global logger # noqa: disable=PLW0603 + global logger # noqa: PLW0602 # receive the message from the syslog logging client message = bytes.decode( self.request[0].strip(), encoding=constants.server.Utf8_Encoding @@ -38,7 +38,7 @@ def handle(self): def start_syslog_server(): """Start a syslog server.""" - global logger # noqa: disable=PLW0603 + global logger # noqa: PLW0602 # always log all of the messages to a file logger.setLevel(logging.DEBUG) # create a RotatingFileHandler such that: diff --git a/datasette/metadata.json b/datasette/metadata.json index 481aa343..a9b0e92e 100644 --- a/datasette/metadata.json +++ b/datasette/metadata.json @@ -35,12 +35,13 @@ THEN substr(context, 1, instr(context, '[') - 1) ELSE context + JSON.stringify(obj, projectname) END AS stripped_text FROM context;" }, } } - + } } diff --git a/poetry.lock b/poetry.lock index 2b35cccd..56843bad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiofiles" @@ -275,6 +275,7 @@ files = [ {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] @@ -376,6 +377,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -842,6 +854,17 @@ sqlite-regex = "*" [package.extras] test = ["pytest"] +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + [[package]] name = "elementpath" version = "4.1.5" @@ -870,6 +893,22 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "flask" version = "3.0.0" @@ -1167,6 +1206,20 @@ files = [ hypothesis = ">=6.31.6" jsonschema = ">=4.0.0" +[[package]] +name = "identify" +version = "2.5.32" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, + {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -1987,6 +2040,20 @@ files = [ {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "numpy" version = "1.26.2" @@ -2293,6 +2360,24 @@ files = [ {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, ] +[[package]] +name = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "psutil" version = "5.9.6" @@ -2990,28 +3075,28 @@ files = [ [[package]] name = "ruff" -version = "0.0.277" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.7" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.277-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:3250b24333ef419b7a232080d9724ccc4d2da1dbbe4ce85c4caa2290d83200f8"}, - {file = "ruff-0.0.277-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3e60605e07482183ba1c1b7237eca827bd6cbd3535fe8a4ede28cbe2a323cb97"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7baa97c3d7186e5ed4d5d4f6834d759a27e56cf7d5874b98c507335f0ad5aadb"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74e4b206cb24f2e98a615f87dbe0bde18105217cbcc8eb785bb05a644855ba50"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:479864a3ccd8a6a20a37a6e7577bdc2406868ee80b1e65605478ad3b8eb2ba0b"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:468bfb0a7567443cec3d03cf408d6f562b52f30c3c29df19927f1e0e13a40cd7"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f32ec416c24542ca2f9cc8c8b65b84560530d338aaf247a4a78e74b99cd476b4"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14a7b2f00f149c5a295f188a643ac25226ff8a4d08f7a62b1d4b0a1dc9f9b85c"}, - {file = "ruff-0.0.277-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9879f59f763cc5628aa01c31ad256a0f4dc61a29355c7315b83c2a5aac932b5"}, - {file = "ruff-0.0.277-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f612e0a14b3d145d90eb6ead990064e22f6f27281d847237560b4e10bf2251f3"}, - {file = "ruff-0.0.277-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:323b674c98078be9aaded5b8b51c0d9c424486566fb6ec18439b496ce79e5998"}, - {file = "ruff-0.0.277-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3a43fbe026ca1a2a8c45aa0d600a0116bec4dfa6f8bf0c3b871ecda51ef2b5dd"}, - {file = "ruff-0.0.277-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:734165ea8feb81b0d53e3bf523adc2413fdb76f1264cde99555161dd5a725522"}, - {file = "ruff-0.0.277-py3-none-win32.whl", hash = "sha256:88d0f2afb2e0c26ac1120e7061ddda2a566196ec4007bd66d558f13b374b9efc"}, - {file = "ruff-0.0.277-py3-none-win_amd64.whl", hash = "sha256:6fe81732f788894a00f6ade1fe69e996cc9e485b7c35b0f53fb00284397284b2"}, - {file = "ruff-0.0.277-py3-none-win_arm64.whl", hash = "sha256:2d4444c60f2e705c14cd802b55cd2b561d25bf4311702c463a002392d3116b22"}, - {file = "ruff-0.0.277.tar.gz", hash = "sha256:2dab13cdedbf3af6d4427c07f47143746b6b95d9e4a254ac369a0edb9280a0d2"}, + {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f80496854fdc65b6659c271d2c26e90d4d401e6a4a31908e7e334fab4645aac"}, + {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1ea109bdb23c2a4413f397ebd8ac32cb498bee234d4191ae1a310af760e5d287"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0c2de9dd9daf5e07624c24add25c3a490dbf74b0e9bca4145c632457b3b42a"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69a4bed13bc1d5dabf3902522b5a2aadfebe28226c6269694283c3b0cecb45fd"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de02ca331f2143195a712983a57137c5ec0f10acc4aa81f7c1f86519e52b92a1"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45b38c3f8788a65e6a2cab02e0f7adfa88872696839d9882c13b7e2f35d64c5f"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c64cb67b2025b1ac6d58e5ffca8f7b3f7fd921f35e78198411237e4f0db8e73"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dcc6bb2f4df59cb5b4b40ff14be7d57012179d69c6565c1da0d1f013d29951b"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2bb4bb6bbe921f6b4f5b6fdd8d8468c940731cb9406f274ae8c5ed7a78c478"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:276a89bcb149b3d8c1b11d91aa81898fe698900ed553a08129b38d9d6570e717"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:90c958fe950735041f1c80d21b42184f1072cc3975d05e736e8d66fc377119ea"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b05e3b123f93bb4146a761b7a7d57af8cb7384ccb2502d29d736eaade0db519"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:290ecab680dce94affebefe0bbca2322a6277e83d4f29234627e0f8f6b4fa9ce"}, + {file = "ruff-0.1.7-py3-none-win32.whl", hash = "sha256:416dfd0bd45d1a2baa3b1b07b1b9758e7d993c256d3e51dc6e03a5e7901c7d80"}, + {file = "ruff-0.1.7-py3-none-win_amd64.whl", hash = "sha256:4af95fd1d3b001fc41325064336db36e3d27d2004cdb6d21fd617d45a172dd96"}, + {file = "ruff-0.1.7-py3-none-win_arm64.whl", hash = "sha256:0683b7bfbb95e6df3c7c04fe9d78f631f8e8ba4868dfc932d43d690698057e2e"}, + {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, ] [[package]] @@ -3442,6 +3527,26 @@ h11 = ">=0.8" [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +[[package]] +name = "virtualenv" +version = "20.24.7" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "werkzeug" version = "3.0.1" @@ -3575,4 +3680,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "919229c283aae8adf17519b2722864f93f07b41cee22c9cced339ef555027dd0" +content-hash = "de2cf9d7203422aa78a6c8a26d8b893b6bf1ac81ef0548ce0272540b7b57e67a" diff --git a/pyproject.toml b/pyproject.toml index bb7654a6..edc90795 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ numpy = "^1.25.2" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" black = "^23.3.0" -ruff = "^0.0.277" +ruff = "^0.1.7" taskipy = "^1.11.0" mypy = "^1.4.1" isort = "^5.12.0" @@ -53,6 +53,7 @@ types-pyyaml = "^6.0.12.10" types-jsonschema = "^4.17.0.9" types-requests = "^2.31.0.10" hypothesis-jsonschema = "^0.22.1" +pre-commit = "^3.5.0" pytest-clarity = "^1.0.1" pytest-randomly = "^3.13.0" pytest-pretty = "^1.2.0" @@ -119,6 +120,7 @@ test-api = { cmd = "{openai-test}", help = "Run openai powered test cases", use_ not-openai-test = { cmd = "{not-openai-test}", help = "Run openai powered test cases", use_vars = true } test-coverage = { cmd = "{coverage-test-command}", help = "Run coverage monitoring for the test suite", use_vars = true } test-coverage-silent = { cmd = "{coverage-test-command-silent}", help = "Run coverage monitoring for tests without output", use_vars = true } +pre-commit-install = { cmd = "pre-commit install", help = "Install or update pre-commit hooks" } [build-system] requires = ["poetry-core"] diff --git a/tests/test_checks.py b/tests/test_checks.py index 9c1dd093..0e2ec8a9 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -7,8 +7,10 @@ from chasten.checks import ( check_match_count, + extract_description, extract_min_max, is_in_closed_interval, + make_checks_status_message, ) from chasten.validate import JSON_SCHEMA_CHECKS @@ -59,6 +61,41 @@ def test_extract_min_max_missing(): assert max_count is None +def test_extract_description(): + """Confirm that if a description exists, it is properly retrieved.""" + check = { + "name": "test", + "description": "described test", + "count": { + "min": 1, + }, + } + assert "described test" == extract_description(check) + + +def test_extract_desription_none(): + """Confirm that if a description does not exist, an empty string is returned.""" + check = { + "name": "test", + "count": { + "min": 1, + }, + } + assert "" == extract_description(check) + + +@pytest.mark.parametrize( + "bool_status,expected", + [ + (True, ":smiley: Did the check pass? Yes"), + (False, ":worried: Did the check pass? No"), + ], +) +def test_make_checks_status_message(bool_status: bool, expected: str): + """Confirms the output matches the expected message.""" + assert make_checks_status_message(bool_status) == expected + + @given(st.dictionaries(st.text(), st.integers())) @pytest.mark.fuzz def test_extract_min_max_hypothesis(check): @@ -89,6 +126,8 @@ def test_integers(check): (1, None, None, True), (5, 6, 4, False), (1, 4, 6, False), + (1, 2, None, False), + (3, None, 2, False), ], ) def test_check_match_count_expected(count, min_value, max_value, expected): diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 00000000..7f6db2e1 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,16 @@ +"""Pytest test suite for the database module.""" + +import os + +from chasten import database + + +def test_create_chasten_view(): + """Confirm that the function creating and viewing an example database does not crash""" + # define the variable name for the example database + chasten_database_name: str = ".example_database" + # create the database with example name + # run the view command with a set SQL query + database.create_chasten_view(chasten_database_name) + # remove the example variable made + os.remove(".example_database") diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py index ac3289f3..5cd032f2 100644 --- a/tests/test_filesystem.py +++ b/tests/test_filesystem.py @@ -25,6 +25,7 @@ def test_invalid_directory() -> None: directory = pathlib.Path(directory_str) confirmation = filesystem.confirm_valid_directory(directory) assert confirmation is False + assert filesystem.confirm_valid_directory(None) is False def test_valid_file() -> None: @@ -41,6 +42,7 @@ def test_invalid_file() -> None: this_file_not = pathlib.Path(file_str) confirmation = filesystem.confirm_valid_file(this_file_not) assert confirmation is False + assert filesystem.confirm_valid_file(None) is False @given(directory=strategies.builds(pathlib.Path))