diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4375d89 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + enable-cache: true + cache-dependency-glob: "pyproject.toml" + cache-suffix: ${{ steps.date.outputs.date }} + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install dependencies + run: | + uv venv --python ${{ matrix.python-version }} + uv pip install -e . + + - name: Run tests + run: uv run python ipgroup_test.py -v + + - name: Install coverage + run: uv pip install coverage coveralls + + - name: Run tests with coverage + run: uv run coverage run ipgroup_test.py + + - name: Upload coverage to Coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + uv run coveralls --service=github + continue-on-error: true + + build: + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + enable-cache: true + cache-dependency-glob: "pyproject.toml" + cache-suffix: ${{ steps.date.outputs.date }} + + - name: Set up Python + run: uv python install 3.13 + + - name: Build package + run: uv build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a7fbedf --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,78 @@ +name: Publish to PyPI + +on: + push: + tags: + - '*' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install dependencies + run: | + uv venv --python ${{ matrix.python-version }} + uv pip install -e . + + - name: Run tests + run: uv run python ipgroup_test.py -v + + build: + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python + run: uv python install 3.13 + + - name: Build package + run: uv build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish: + runs-on: ubuntu-latest + needs: build + permissions: + id-token: write # Required for trusted publishing to PyPI + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 2ecb5f8..c00289c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,10 @@ nosetests.xml *,cover .idea/ + +# Virtual environments +.venv/ +venv/ + +# uv lock file (not needed for libraries) +uv.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 12f886b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -dist: xenial -language: python -sudo: false -python: -- '3.5' -- '3.6' -- '3.7' -- '3.8-dev' -- 'nightly' -env: -- PYTHONPATH=. -install: -- pip install coveralls -script: coverage run ipgroup_test.py -after_success: coveralls -deploy: - provider: pypi - user: rypeck - password: - secure: ITMC3cQXglwDp8jvQxyduOwWdHtu5lT5YSuSkw1AwD4LxnWcH+sgnOM6+USAjS9ug43DvzuRHZ6v3auMcZ2tKwWLceaahSHzIsZOcUe5mPp/fXCqkxwLnT+W++LsBCQUajo6aGIReKOo+q9CdQcSzpyCvgo8xLNX/syXwPkrHJ4= - on: - branch: master - tags: true - python: 3.7 - repo: RyPeck/python-ipgroup diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db6822b --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +.PHONY: help test test-verbose build clean install dev lint format check-format + +help: + @echo "Available targets:" + @echo " test - Run tests using uv" + @echo " test-verbose - Run tests with verbose output" + @echo " build - Build the package using uv" + @echo " clean - Remove build artifacts and cache files" + @echo " install - Install package in development mode" + @echo " dev - Install development dependencies" + @echo " lint - Run linting (if configured)" + @echo " format - Format code (if configured)" + @echo " check-format - Check code formatting" + +test: + uv run python ipgroup_test.py + +test-verbose: + uv run python ipgroup_test.py -v + +build: clean + uv build + +clean: + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info + rm -rf .pytest_cache + rm -rf __pycache__ + find . -type d -name "__pycache__" -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + find . -type f -name "*.pyo" -delete + find . -type f -name "*~" -delete + +install: + uv pip install -e . + +dev: + uv pip install -e ".[dev]" + +lint: + @echo "No linting configured yet. Install ruff or flake8 to enable linting." + +format: + @echo "No formatting configured yet. Install ruff or black to enable formatting." + +check-format: + @echo "No format checking configured yet." diff --git a/README.md b/README.md index 06069f8..c709057 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ IP Grouping Python ================== -[![Build -Status](https://travis-ci.org/RyPeck/python-ipgroup.svg?branch=master)](https://travis-ci.org/RyPeck/python-ipgroup) [![Coverage -Status](https://coveralls.io/repos/RyPeck/python-ipgroup/badge.png)](https://coveralls.io/r/RyPeck/python-ipgroup) [![Downloads](https://pypip.in/download/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/ ) [![License](https://pypip.in/license/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) [![Supported Python versions](https://pypip.in/py_versions/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) [![Development Status](https://pypip.in/status/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) +[![Coverage Status](https://coveralls.io/repos/RyPeck/python-ipgroup/badge.png)](https://coveralls.io/r/RyPeck/python-ipgroup) [![Downloads](https://pypip.in/download/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/ ) [![License](https://pypip.in/license/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) [![Supported Python versions](https://pypip.in/py_versions/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) [![Development Status](https://pypip.in/status/ipgroup/badge.svg)](https://pypi.python.org/pypi/ipgroup/) ## Usage diff --git a/ipgroup.py b/ipgroup.py index a711c41..3a29a22 100644 --- a/ipgroup.py +++ b/ipgroup.py @@ -116,12 +116,16 @@ def _validate_ips_param(self, ips): """ # Acceptable inputs - assert(isinstance(ips, (str, list, self.IPVersion))) + valid_types = (str, list) + if self.IPVersion is not None: + valid_types = valid_types + self.IPVersion + assert(isinstance(ips, valid_types)) # Unpack a list if isinstance(ips, list): for i in ips: - assert(isinstance(i, (str, ipaddress._IPAddressBase))) + assert(isinstance(i, (str, ipaddress.IPv4Address, ipaddress.IPv6Address, + ipaddress.IPv4Network, ipaddress.IPv6Network))) if isinstance(i, str): assert(self._validate_IPNetwork_str(i)) @@ -197,14 +201,14 @@ class IPv4Group(_BaseGroup): """Group of IPv4 Addresses""" def __init__(self, ip_objs, net_bits=24): - _BaseGroup.__init__(self, ip_objs, net_bits, ipaddress._BaseV4) + _BaseGroup.__init__(self, ip_objs, net_bits, (ipaddress.IPv4Network, ipaddress.IPv4Address)) class IPv6Group(_BaseGroup): """Group of IPv6 Addresses""" def __init__(self, ip_objs, net_bits=48): - _BaseGroup.__init__(self, ip_objs, net_bits, ipaddress._BaseV6) + _BaseGroup.__init__(self, ip_objs, net_bits, (ipaddress.IPv6Network, ipaddress.IPv6Address)) def totalAddresses(ips): diff --git a/ipgroup_test.py b/ipgroup_test.py index 40179a2..854ab29 100755 --- a/ipgroup_test.py +++ b/ipgroup_test.py @@ -61,7 +61,7 @@ def test_group3(self): # So out sample size is never bigger than the population of hosts random_int = random.randint(1, 2**(32 - random_cidr - 1)) - IPs = random.sample(set(network.hosts()), random_int) + IPs = random.sample(list(network.hosts()), random_int) expected_results = {("129.21.0.0/%s" % random_cidr): random_int} @@ -81,7 +81,7 @@ def test_IPv6(self): # So out sample size is never bigger than the population of hosts random_int = random.randint(1, 2**(128 - random_cidr - 1)) - IPs = random.sample(set(network.hosts()), random_int) + IPs = random.sample(list(network.hosts()), random_int) expected_results = {("2607:f8b0:4009:803::/%s" % random_cidr): random_int} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..62dada9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "ipgroup" +version = "0.0.5" +description = "Functions to gather info on a group of IPv4 or IPv6 Networks" +readme = "README.md" +requires-python = ">=3.9" +license = "Apache-2.0" +authors = [ + { name = "Ryan Peck", email = "ryan@rypeck.com" }, +] +maintainers = [ + { name = "Ryan Peck", email = "ryan@rypeck.com" }, +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Internet", + "Topic :: Internet :: Log Analysis", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Networking", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://github.com/RyPeck/python-ipgroup" +Repository = "https://github.com/RyPeck/python-ipgroup" +Issues = "https://github.com/RyPeck/python-ipgroup/issues" + +[tool.hatch.build.targets.wheel] +packages = ["ipgroup.py"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 6d3963d..0000000 --- a/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2019 Ryan Peck -# -# Licensed 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. - -from setuptools import setup -from os import path - -import ipgroup - - -this_directory = path.abspath(path.dirname(__file__)) -with open(path.join(this_directory, "README.md"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="ipgroup", - maintainer="Ryan Peck", - maintainer_email="ryan@rypeck.com", - version=ipgroup.__version__, - url="https://github.com/RyPeck/python-ipgroup", - license="Apache License, Version 2.0", - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "Intended Audience :: System Administrators", - "Intended Audience :: Telecommunications Industry", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Internet", - "Topic :: Internet :: Log Analysis", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Networking", - "Topic :: Utilities", - ], - description="Functions to gather info on a group of IPv4 or IPv6 Networks", - py_modules=["ipgroup"], - long_description=long_description, - long_description_content_type="text/markdown", -)