diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 515c612..063fd73 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python: ["3.10", "3.11", "3.12", "3.13"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ${{ matrix.os }} steps: - name: Checkout Repository diff --git a/README.md b/README.md index abcbeb3..e9f75ea 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Why a new PyPi package? Well, the not-so-great PyPi doesn't allow to have forked # PyCG - Practical Python Call Graphs -[![Tests](https://github.com/deeplime-io/PyCG/actions/workflows/test.yaml/badge.svg)](https://github.com/vitsalis/PyCG/actions/workflows/test.yaml) +[![Tests](https://github.com/deeplime-io/PyCG/actions/workflows/test.yaml/badge.svg)](https://github.com/deeplime-io/PyCG/actions/workflows/test.yaml) PyCG generates call graphs for Python code using static analysis. It efficiently supports @@ -33,7 +33,7 @@ In _43rd International Conference on Software Engineering, ICSE '21_, # Installation -PyCG is implemented in Python3 and requires Python version 3.4 or higher. +PyCG is implemented in Python3 and requires Python 3.10–3.14. It also has no dependencies. Simply: ``` pip install onecode-pycg diff --git a/pycg/processing/preprocessor.py b/pycg/processing/preprocessor.py index 5d5c7db..95e8cca 100644 --- a/pycg/processing/preprocessor.py +++ b/pycg/processing/preprocessor.py @@ -50,13 +50,14 @@ def __init__( def _get_fun_defaults(self, node): defaults = {} - start = len(node.args.args) - len(node.args.defaults) + pos_args = node.args.posonlyargs + node.args.args + start = len(pos_args) - len(node.args.defaults) for cnt, d in enumerate(node.args.defaults, start=start): if not d: continue self.visit(d) - defaults[node.args.args[cnt].arg] = self.decode_node(d) + defaults[pos_args[cnt].arg] = self.decode_node(d) start = len(node.args.kwonlyargs) - len(node.args.kw_defaults) for cnt, d in enumerate(node.args.kw_defaults, start=start): diff --git a/pycg/tests/preprocessor_test.py b/pycg/tests/preprocessor_test.py new file mode 100644 index 0000000..926b530 --- /dev/null +++ b/pycg/tests/preprocessor_test.py @@ -0,0 +1,69 @@ +# +# Copyright (c) 2020 Vitalis Salis. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import ast +import os +import tempfile + +from base import TestBase + +from pycg.machinery.classes import ClassManager +from pycg.machinery.definitions import DefinitionManager +from pycg.machinery.imports import ImportManager +from pycg.machinery.modules import ModuleManager +from pycg.machinery.scopes import ScopeManager +from pycg.processing.preprocessor import PreProcessor + + +class PreprocessorTest(TestBase): + def _make_preprocessor(self, code): + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write(code) + fname = f.name + + im = ImportManager() + sm = ScopeManager() + dm = DefinitionManager() + cm = ClassManager() + mm = ModuleManager() + pp = PreProcessor(fname, "mod", im, sm, dm, cm, mm, modules_analyzed=set()) + return pp, fname + + def test_get_fun_defaults_posonlyargs(self): + code = "def eye(n_rows, n_cols=None, /): pass\n" + pp, fname = self._make_preprocessor(code) + try: + node = ast.parse(code).body[0] + defaults = pp._get_fun_defaults(node) + self.assertIn("n_cols", defaults) + self.assertEqual(defaults["n_cols"], [None]) + finally: + os.unlink(fname) + + def test_get_fun_defaults_positional_or_keyword(self): + code = "def f(a, b=1): pass\n" + pp, fname = self._make_preprocessor(code) + try: + node = ast.parse(code).body[0] + defaults = pp._get_fun_defaults(node) + self.assertIn("b", defaults) + self.assertEqual(defaults["b"], [1]) + finally: + os.unlink(fname) diff --git a/pyproject.toml b/pyproject.toml index 1a84df6..2ff82a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,32 @@ [project] name = "onecode-pycg" -version = "1.2.0" - +version = "1.2.1" description = "PyCG - Practical Python Call Graphs" readme = "README.md" -requires-python = ">=3.4" - -licence = { file = "LICENCE" } +requires-python = ">=3.10,<3.15" +license = { file = "LICENCE" } authors = [{ name = "Vitalis Salis", email = "vitsalis@gmail.com" }] - - +maintainers = [{ name = "DeepLime", email = "contact@deeplime.io" }] classifiers = [ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] +[project.scripts] +pycg = "pycg.__main__:main" + [project.optional-dependencies] -dev = ["flake8>=6.0.0", "isort>=5.12.0", "black>=22.12.0", "mock"] +dev = ["ruff>=0.8.0", "mock"] [project.urls] -"Homepage" = "https://github.com/vitsalis/PyCG" -"Bug Tracker" = "https://github.com/vitsalis/PyCG/issues" +Homepage = "https://github.com/deeplime-io/PyCG" +"Bug Tracker" = "https://github.com/deeplime-io/PyCG/issues" +Repository = "https://github.com/deeplime-io/PyCG" [build-system] requires = ["hatchling"] @@ -29,33 +35,14 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["pycg"] -[tool.black] -line-length = 88 -target_version = ['py38', 'py39', 'py310', 'py311', 'py312'] -preview = true -exclude = ''' -/( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.mypy_cache - | \.vscode - | build - | dist - | micro-benchmark - | micro-benchmark-key-errs -)/ -''' - [tool.ruff] +exclude = ["micro-benchmark", "micro-benchmark-key-errs"] +target-version = "py310" + +[tool.ruff.lint] # Do not enforce `E501` (line length violations) for now. ignore = ["E501"] -exclude = ["micro-benchmark", "micro-benchmark-key-errs"] - # Ignore `E402` (import violations) in all `__init__.py` files -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401"] - - -[tool.isort] -profile = "black" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index ccb9496..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -flake8>=6.0.0 -isort>=5.12.0 -black>=22.12.0 -mock \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e69de29..0000000 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100644 index 76007b3..0000000 --- a/setup.py +++ /dev/null @@ -1,61 +0,0 @@ -# -# Copyright (c) 2020 Vitalis Salis. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -import os - -from setuptools import setup, find_packages -from subprocess import call - - -def get_long_desc(): - with open("README.md", "r") as readme: - desc = readme.read() - - return desc - - -def setup_package(): - setup( - name="onecode-pycg", - version="1.2.0", - description="Practical Python Call Graphs", - long_description=get_long_desc(), - long_description_content_type="text/markdown", - url="https://github.com/deeplime-io/pycg", - license="Apache Software License", - packages=find_packages(), - install_requires=[], - python_requires=">=3.4", - entry_points={ - "console_scripts": [ - "pycg=pycg.__main__:main", - ], - }, - classifiers=[ - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3", - ], - author="Vitalis Salis", - author_email="vitsalis@gmail.com", - ) - - -if __name__ == "__main__": - setup_package() diff --git a/setup_devenv.sh b/setup_devenv.sh deleted file mode 100644 index 5c5f98b..0000000 --- a/setup_devenv.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash -#encoding=utf8 - -function echo_block() { - echo "----------------------------" - echo $1 - echo "----------------------------" -} - -function check_installed_pip() { - ${PYTHON} -m pip > /dev/null - if [ $? -ne 0 ]; then - echo_block "Installing Pip for ${PYTHON}" - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py - ${PYTHON} get-pip.py - rm get-pip.py - fi -} - -# Check which python version is installed -function check_installed_python() { - if [ -n "${VIRTUAL_ENV}" ]; then - echo "Please deactivate your virtual environment before running setup.sh." - echo "You can do this by running 'deactivate'." - exit 2 - fi - - for v in 10 - do - PYTHON="python3.${v}" - which $PYTHON - if [ $? -eq 0 ]; then - echo "using ${PYTHON}" - check_installed_pip - return - fi - done - - echo "No usable python found. Please make sure to have python3.10 or newer installed." - exit 1 -} - -function updateenv() { - echo_block "Updating your virtual env" - if [ ! -f .env/bin/activate ]; then - echo "Something went wrong, no virtual environment found." - exit 1 - fi - source .env/bin/activate - SYS_ARCH=$(uname -m) - echo "pip install in-progress. Please wait..." - ${PYTHON} -m pip install --upgrade pip wheel setuptools - REQUIREMENTS=requirements-dev.txt - - ${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} - if [ $? -ne 0 ]; then - echo "Failed installing dependencies" - exit 1 - fi - ${PYTHON} -m pip install -e . - if [ $? -ne 0 ]; then - echo "Failed installing PyCG" - exit 1 - fi - - echo "pip install completed" -} - -# Install bot Debian_ubuntu -function install_debian() { - sudo apt-get update - sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git curl $(echo lib${PYTHON}-dev ${PYTHON}-venv) -} - -# Upgrade PyCG -function update() { - git pull - updateenv -} - -function check_git_changes() { - if [ -z "$(git status --porcelain)" ]; then - echo "No changes in git directory" - return 1 - else - echo "Changes in git directory" - return 0 - fi -} - -# Reset Develop or Stable branch -function reset() { - echo_block "Resetting virtual env" - - if [ -d ".env" ]; then - echo "- Deleting your previous virtual env" - rm -rf .env - fi - echo - ${PYTHON} -m venv .env - if [ $? -ne 0 ]; then - echo "Could not create virtual environment. Leaving now" - exit 1 - fi - updateenv -} - -function install() { - echo_block "Installing mandatory dependencies" - - if [ -x "$(command -v apt-get)" ]; then - echo "Debian/Ubuntu detected. Setup for this system in-progress" - install_debian - else - echo "This script does not support your OS." - echo "If you have Python version 3.10, pip, virtualenv you can continue." - echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." - sleep 10 - fi - echo - reset -} - -function help() { - echo "usage:" - echo " -i,--install Install PyCG from scratch" - echo " -u,--update Command git pull to update." - echo " -r,--reset Hard reset your develop/stable branch." -} - -# Verify if 3.10 is installed -check_installed_python - -case $* in ---install|-i) -install -;; ---update|-u) -update -;; ---reset|-r) -reset -;; -*) -help -;; -esac -exit 0