diff --git a/.readthedocs.yml b/.readthedocs.yml index 46de5ce2..c776d87b 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,6 +13,8 @@ python: install: - method: pip path: . + extre_requirements: + - docs conda: environment: docs/source/environment.yml diff --git a/pyproject.toml b/pyproject.toml index 95c1f12f..7172149a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,34 +1,119 @@ [build-system] -requires = [ - "hatchling" -] +requires = ["hatchling"] build-backend = "hatchling.build" +[project] +name = "Mesa-Geo" +description = "GIS Agent-based modeling (ABM) in Python" +license = { text = "Apache 2.0" } +requires-python = ">=3.10" +authors = [ + { name = "Project Mesa Team", email = "projectmesa@googlegroups.com" }, +] +keywords = [ + "agent", + "based", + "modeling", + "model", + "ABM", + "simulation", + "multi-agent", +] +classifiers = [ + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Life", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Natural Language :: English", +] +readme = "README.md" +dependencies = [ + "mesa", + "geopandas", + "libpysal", + "rtree", + "rasterio", + "shapely", + "pyproj", + "folium", + "xyzservices>=2022.9.0", + "ipyleaflet" +] +dynamic = ["version"] + +[project.optional-dependencies] +dev = [ + "ruff~=0.1.1", # Update periodically + "coverage", + "pytest >= 4.6", + "pytest-cov", + "sphinx", + "pytest-mock", +] +docs = [ + "sphinx", + "ipython", + "pydata_sphinx_theme", + "seaborn", + "myst-nb", + "myst-parser", # Markdown in Sphinx + "jupyterlite_sphinx", + "jupyterlite-xeus>=0.2.0a0", + "jupyter_bridge" +] + +[project.urls] +homepage = "https://github.com/projectmesa/mesa-geo" +repository = "https://github.com/projectmesa/mesa-geo" + +[project.scripts] +mesa = "mesa.main:cli" + +[tool.hatch.build.targets.wheel] +packages = ["mesa-geo"] + +[tool.hatch.version] +path = "mesa_geo/__init__.py" + [tool.ruff] +# See https://github.com/charliermarsh/ruff#rules for error code definitions. +# Hardcode to Python 3.10. +# Reminder to update mesa-examples if the value below is changed. +target-version = "py310" +extend-exclude = ["docs", "build"] + +[tool.ruff.lint] select = [ - # "ANN", # annotations TODO - "B", # bugbear - "C4", # comprehensions - "DTZ", # naive datetime - "E", # style errors - "F", # flakes - "I", # import sorting - "ISC", # string concatenation - "N", # naming - "PGH", # pygrep-hooks - "PIE", # miscellaneous - "PLC", # pylint convention - "PLE", # pylint error - # "PLR", # pylint refactor TODO - "PLW", # pylint warning - "Q", # quotes - "RUF", # Ruff - "S", # security - "SIM", # simplify - "T10", # debugger - "UP", # upgrade - "W", # style warnings - "YTT", # sys.version + # "ANN", # annotations TODO + "B", # bugbear + "C4", # comprehensions + "DTZ", # naive datetime + "E", # style errors + "F", # flakes + "I", # import sorting + "ISC", # string concatenation + "N", # naming + "PGH", # pygrep-hooks + "PIE", # miscellaneous + "PLC", # pylint convention + "PLE", # pylint error + # "PLR", # pylint refactor TODO + "PLW", # pylint warning + "Q", # quotes + "RUF", # Ruff + "S", # security + "SIM", # simplify + "T10", # debugger + "UP", # upgrade + "W", # style warnings + "YTT", # sys.version ] # Ignore list taken from https://github.com/psf/black/blob/master/.flake8 # E203 Whitespace before ':' @@ -39,21 +124,16 @@ select = [ # checks for it. # See https://github.com/charliermarsh/ruff/issues/1842#issuecomment-1381210185 extend-ignore = [ - "E501", - "S101", # Use of `assert` detected - "B017", # `assertRaises(Exception)` should be considered evil TODO - "PGH004", # Use specific rule codes when using `noqa` TODO - "B905", # `zip()` without an explicit `strict=` parameter - "N802", # Function name should be lowercase - "N999", # Invalid module name. We should revisit this in the future, TODO - "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` TODO - "S310", # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected. - "S603", # `subprocess` call: check for execution of untrusted input - "ISC001", # ruff format asks to disable this feature -] - -#ruff ignores build and docs -extend-exclude = ["docs", "build"] - -# Hardcode to Python 3.10. -target-version = "py310" \ No newline at end of file + "E501", + "S101", # Use of `assert` detected + "B017", # `assertRaises(Exception)` should be considered evil TODO + "PGH004", # Use specific rule codes when using `noqa` TODO + "B905", # `zip()` without an explicit `strict=` parameter + "N802", # Function name should be lowercase + "N999", # Invalid module name. We should revisit this in the future, TODO + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` TODO + "S310", # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected. + "S603", # `subprocess` call: check for execution of untrusted input + "ISC001", # ruff format asks to disable this feature + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes +] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 026fbae3..00000000 --- a/setup.cfg +++ /dev/null @@ -1,66 +0,0 @@ -[metadata] -name = Mesa-Geo -url= https://github.com/projectmesa/mesa-geo -project_urls = - Docs: RTD = https://mesa-geo.readthedocs.io/en/main - CI: GitHub = https://github.com/projectmesa/mesa-geo/actions - GitHub: issues = https://github.com/projectmesa/mesa-geo/issues - GitHub: repo = https://github.com/projectmesa/mesa-geo -description = GIS extension for the Mesa agent-based modeling framework in Python -long_description = file: README.md -long_description_content_type = text/markdown -keywords = agent-based modeling, geographic information system, python, complex system -author = Project Mesa-Geo Team -zip_safe = False -license_files = LICENSE -license = Apache License Version 2.0 -classifiers = - Topic :: Scientific/Engineering - Topic :: Scientific/Engineering :: Artificial Life - Topic :: Scientific/Engineering :: Artificial Intelligence - Intended Audience :: Science/Research - Programming Language :: Python :: 3 :: Only - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Development Status :: 3 - Alpha - Natural Language :: English - -[options] -python_requires = >=3.10 -packages = find: -include_package_data = True -install_requires = - mesa~=2.3.0 - geopandas - libpysal - rtree - rasterio - shapely - numpy - pyproj - folium - xyzservices>=2022.9.0 - ipyleaflet - -[options.package_data] -mesa_geo = *.md,*.ipynb,*.rst - -[options.extras_require] -dev = - black - coverage - pytest - pytest-cov - ruff==0.0.254 -docs = - sphinx - sphinx_rtd_theme - jupyterlite-xeus>=0.2.0a0 - jupyter_bridge - - - -[options.packages.find] -exclude = - examples* - tests* diff --git a/setup.py b/setup.py deleted file mode 100644 index 2a9a7eda..00000000 --- a/setup.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env python -import base64 -import hashlib -import os -import re -import shutil -import subprocess -import urllib.request -from pathlib import Path - -from setuptools import Command, setup -from setuptools.command.build_py import build_py - - -def get_version_from_package() -> str: - with open("mesa_geo/__init__.py") as fd: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE - ).group(1) - return version - - -class BuildPyCommand(build_py): - def run(self): - get_frontend_dep() - build_py.run(self) - - -class BuildDocsCommand(Command): - description = "Build the documentation using Sphinx" - user_options = [] - - def build_docs(self): - print("build_docs called") - return ["sphinx-build", "-W", "-b", "html", "docs/source", "docs/build/html"] - - def run(self): - subprocess.check_call(self.build_docs()) # necessary to pass ruff - - -class ServeDocsCommand(Command): - description = "Serve the documentation using http.server" - user_options = [] - - def serve_docs(self): - print("SERVE DOCS called)") - return ["python", "-m", "http.server", "--directory docs/build/html"] - - def run(self): - os.chdir("docs/build/html") - subprocess.check_call(self.serve_docs()) # necessary to pass ruff - - -def get_frontend_dep(): - # Important: Make sure to update the integrity_hash together with the new version number, - # otherwise the previous file is going to be kept and used. - leaflet_version = "1.8.0" - ensure_frontend_dep_single( - f"https://unpkg.com/leaflet@{leaflet_version}/dist/leaflet.js", - external_dir_single=Path("mesa_geo/visualization/templates/js/external"), - integrity_hash="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==", - ) - ensure_frontend_dep_single( - f"https://unpkg.com/leaflet@{leaflet_version}/dist/leaflet.css", - external_dir_single=Path("mesa_geo/visualization/templates/css/external"), - integrity_hash="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==", - ) - - -def make_hash(filepath): - with open(filepath, "rb") as f: - file_as_bytes = f.read() - file_hash = base64.b64encode(hashlib.sha512(file_as_bytes).digest()) - return "sha512-" + file_hash.decode() - - -def ensure_frontend_dep_single( - url, external_dir_single, out_name=None, integrity_hash=None -): - external_dir_single.mkdir(parents=True, exist_ok=True) - # Used for downloading e.g. Leaflet single file - if out_name is None: - out_name = url.split("/")[-1] - dst_path = external_dir_single / out_name - print(f"Checking for presence of {dst_path=} {dst_path.is_file()=}") - - if not dst_path.is_file(): - download_frontend_dep_single(url, dst_path, out_name, integrity_hash) - return - - if integrity_hash is None: - # File is present and assumed to be valid - print(f"{out_name} is present and assumed to be valid; not downloading") - return - - if make_hash(dst_path) == integrity_hash: - # File is present and confirmed valid - print("{out_name} is present and confirmed valid; not downloading") - return - - download_frontend_dep_single(url, dst_path, out_name, integrity_hash) - - -def download_frontend_dep_single(url, dst_path, out_name, integrity_hash): - print(f"Downloading the {out_name} dependency from the internet...") - urllib.request.urlretrieve(url, out_name) - if integrity_hash and ((actual_hash := make_hash(out_name)) != integrity_hash): - os.remove(out_name) - raise ValueError( - f"Integrity check failed for {out_name}. Expected {integrity_hash}, " - f"received {actual_hash}." - ) - shutil.move(out_name, dst_path) - - -if __name__ == "__main__": - setup( - name="Mesa-Geo", - version=get_version_from_package(), - cmdclass={ - "build_py": BuildPyCommand, - "build_docs": BuildDocsCommand, - "serve_docs": ServeDocsCommand, - }, - )