element.
148 | # For black navbar, do "navbar navbar-inverse"
149 | #'navbar_class': "navbar navbar-inverse",
150 | # Fix navigation bar to top of page?
151 | # Values: "true" (default) or "false"
152 | "navbar_fixed_top": "true",
153 | # Location of link to source.
154 | # Options are "nav" (default), "footer" or anything else to exclude.
155 | "source_link_position": "footer",
156 | # Bootswatch (http://bootswatch.com/) theme.
157 | #
158 | # Options are nothing (default) or the name of a valid theme
159 | # such as "amelia" or "cosmo", "yeti", "flatly".
160 | "bootswatch_theme": "yeti",
161 | # Choose Bootstrap version.
162 | # Values: "3" (default) or "2" (in quotes)
163 | "bootstrap_version": "3",
164 | # Navigation bar menu
165 | "navbar_links": [
166 | ("Installation", "installation"),
167 | ("API", "api"),
168 | ("References", "references"),
169 | ("Release Notes", "releases")
170 | ],
171 | "logo": {
172 | "text": "PySAL",
173 | "image_light": "_static/images/pysal_logo_light.png",
174 | "image_dark": "_static/images/pysal_logo.png",
175 | }
176 | }
177 |
178 | # Add any paths that contain custom static files (such as style sheets) here,
179 | # relative to this directory. They are copied after the builtin static files,
180 | # so a file named "default.css" will overwrite the builtin "default.css".
181 | html_static_path = ["_static"]
182 |
183 | # Custom sidebar templates, maps document names to template names.
184 | # html_sidebars = {}
185 | #html_sidebars = {'sidebar': ['localtoc.html', 'sourcelink.html', 'searchbox.html']}
186 |
187 |
188 | html_sidebars = {
189 | "installation": [],
190 | "api": [],
191 | "references": [],
192 | }
193 |
194 | # -- Options for HTMLHelp output ------------------------------------------
195 |
196 | # Output file base name for HTML help builder.
197 | htmlhelp_basename = "pysal" + "doc"
198 |
199 |
200 | # -- Options for LaTeX output ---------------------------------------------
201 |
202 | latex_elements = {
203 | # The paper size ('letterpaper' or 'a4paper').
204 | #
205 | # 'papersize': 'letterpaper',
206 | # The font size ('10pt', '11pt' or '12pt').
207 | #
208 | # 'pointsize': '10pt',
209 | # Additional stuff for the LaTeX preamble.
210 | #
211 | # 'preamble': '',
212 | # Latex figure (float) alignment
213 | #
214 | # 'figure_align': 'htbp',
215 | }
216 |
217 | # Grouping the document tree into LaTeX files. List of tuples
218 | # (source start file, target name, title,
219 | # author, documentclass [howto, manual, or own class]).
220 | latex_documents = [
221 | (master_doc, "pysal.tex", u"pysal Documentation", u"pysal developers", "manual")
222 | ]
223 |
224 |
225 | # -- Options for manual page output ---------------------------------------
226 |
227 | # One entry per manual page. List of tuples
228 | # (source start file, name, description, authors, manual section).
229 | man_pages = [(master_doc, "pysal", u"pysal Documentation", [author], 1)]
230 |
231 |
232 | # -- Options for Texinfo output -------------------------------------------
233 |
234 | # Grouping the document tree into Texinfo files. List of tuples
235 | # (source start file, target name, title, author,
236 | # dir menu entry, description, category)
237 | texinfo_documents = [
238 | (
239 | master_doc,
240 | "pysal",
241 | u"pysal Documentation",
242 | author,
243 | "pysal",
244 | "One line description of project.",
245 | "Miscellaneous",
246 | )
247 | ]
248 |
249 |
250 | # -----------------------------------------------------------------------------
251 | # Autosummary
252 | # -----------------------------------------------------------------------------
253 |
254 | # Generate the API documentation when building
255 | autosummary_generate = True
256 | numpydoc_show_class_members = True
257 | class_members_toctree = True
258 | numpydoc_show_inherited_class_members = True
259 | numpydoc_use_plots = True
260 |
261 | # display the source code for Plot directive
262 | plot_include_source = True
263 |
264 |
265 | def setup(app):
266 | app.add_css_file("pysal-styles.css")
267 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. documentation master file
2 |
3 | PySAL: Python Spatial Analysis Library
4 | ======================================
5 | PySAL is an open source
6 | cross-platform library for geospatial data science with an emphasis on
7 | geospatial vector data written in Python.
8 |
9 | .. raw:: html
10 |
11 |
12 |
13 |
14 |
15 |
23 |
31 |
41 |
42 |
43 |
44 |
45 |
46 | PySAL supports the development of
47 | high level applications for spatial analysis, such as
48 |
49 | - detection of spatial clusters, hot-spots, and outliers
50 | - construction of graphs from spatial data
51 | - spatial regression and statistical modeling on geographically
52 | embedded networks
53 | - spatial econometrics
54 | - exploratory spatio-temporal data analysis
55 |
56 | PySAL Components
57 | ================
58 |
59 | - **explore** - modules to conduct exploratory analysis of spatial and spatio-temporal data, including statistical testing on points, networks, and
60 | polygonal lattices. Also includes methods for spatial inequality, distributional dynamics, and segregation.
61 | - **viz** - visualize patterns in spatial data to detect clusters,
62 | outliers, and hot-spots.
63 | - **model** - model spatial relationships in data with a variety of
64 | linear, generalized-linear, generalized-additive, and nonlinear
65 | models.
66 | - **lib** - solve a wide variety of computational geometry problems:
67 |
68 | - graph construction from polygonal lattices, lines, and points.
69 | - construction and interactive editing of spatial weights matrices
70 | & graphs
71 | - computation of alpha shapes, spatial indices, and
72 | spatial-topological relationships
73 | - reading and writing of sparse graph data, as well as pure python
74 | readers of spatial vector data.
75 |
76 |
77 | .. toctree::
78 | :hidden:
79 | :maxdepth: 3
80 | :caption: Contents:
81 |
82 | Installation
83 | API
84 | References
85 |
86 | Details are available in the `PySAL api `_.
87 |
88 | For background information see :cite:`pysal2007, rey2014PythonSpatial, anselin2014ModernSpatial, rey2019pysal, fotheringham2017multiscale, fleischmann_2019, cortes2019OpensourceFramework, wolf2019GeosilhouettesGeographical, yu:2019, rey2020VisualAnalytics, Lumnitz2020, saxon2021OpenSoftware, rey_2021a, Gaboardi2021,rey2022PySALEcosystem, spopt2022, rey2023GeographicData`.
89 |
90 | ***********
91 | Development
92 | ***********
93 |
94 | As of version 2.0.0, PySAL is now a collection of affiliated geographic
95 | data science packages. Changes to the code for any of the subpackages
96 | should be directed at the respective `upstream repositories `_ and not made
97 | here. Infrastructural changes for the meta-package, like those for
98 | tooling, building the package, and code standards, will be considered.
99 |
100 |
101 | PySAL development is hosted on github_.
102 |
103 | .. _github : https://github.com/pysal/PySAL
104 |
105 |
106 |
107 | Discussions of development occurs on the
108 | `developer list `_
109 | as well as discord_.
110 |
111 | .. _discord : https://discord.gg/BxFTEPFFZn
112 |
113 | ****************
114 | Getting Involved
115 | ****************
116 |
117 | If you are interested in contributing to PySAL please see our
118 | `development guidelines `_.
119 |
120 |
121 | ***********
122 | Bug reports
123 | ***********
124 |
125 | To search for or report bugs, please see PySAL's issues_.
126 |
127 | .. _issues : http://github.com/pysal/pysal/issues
128 |
129 |
130 | ***************
131 | Citing PySAL
132 | ***************
133 |
134 | If you use PySAL in a scientific publication, we would appreciate citations to the following paper:
135 |
136 | `PySAL: A Python Library of Spatial Analytical Methods `_, *Rey, S.J. and L. Anselin*, Review of Regional Studies 37, 5-27 2007.
137 |
138 | Bibtex entry::
139 |
140 | @Article{pysal2007,
141 | author={Rey, Sergio J. and Anselin, Luc},
142 | title={{PySAL: A Python Library of Spatial Analytical Methods}},
143 | journal={The Review of Regional Studies},
144 | year=2007,
145 | volume={37},
146 | number={1},
147 | pages={5-27},
148 | keywords={Open Source; Software; Spatial}
149 | }
150 |
151 |
152 |
153 | *******************
154 | License information
155 | *******************
156 |
157 | See the file "LICENSE.txt" for information on the history of this
158 | software, terms & conditions for usage, and a DISCLAIMER OF ALL
159 | WARRANTIES.
160 |
161 |
162 | .. _PySAL: https://github.com/pysal/pysal
163 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | .. Installation
2 |
3 | Installation
4 | ============
5 |
6 |
7 | You can install the meta package `pysal` which results in all the packages in
8 | the `pysal` ecosystem being installed.
9 |
10 | Alternatively, individual packages can be installed a la carte.
11 |
12 |
13 | Installing the pysal meta package
14 | ---------------------------------
15 | .. tab-set-code::
16 |
17 | .. code-block:: pip
18 |
19 | pip install pysal
20 |
21 | .. code-block:: conda
22 |
23 | conda install --channel conda-forge pysal
24 |
25 | Installing individual pysal packages
26 | ------------------------------------
27 | A similar approach can be used to select one, or a subset, of pysal packages
28 | without having to install the entire ecosystem. For example, to install `esda`
29 | and `mapclassify`
30 |
31 | .. tab-set-code::
32 |
33 | .. code-block:: pip
34 |
35 | pip install esda mapclassify
36 |
37 | .. code-block:: conda
38 |
39 | conda install --channel conda-forge esda mapclassify
40 |
41 |
42 |
43 | Installing the development version
44 | ----------------------------------
45 |
46 | Potentially, you might want to use the newest features in the development
47 | version of PySAL on github - `pysal/pysal`_ while have not been incorporated
48 | in the Pypi released version. You can achieve that by installing `pysal/pysal`_
49 | by running the following from a command shell::
50 |
51 | pip install git+https://github.com/pysal/pysal.git
52 |
53 | You can also `fork`_ the `pysal/pysal`_ repo and create a local clone of
54 | your fork. By making changes
55 | to your local clone and submitting a pull request to `pysal/PySAL`_, you can
56 | contribute to the PySAL development.
57 |
58 |
59 | .. _dependencies:
60 |
61 | Dependencies
62 | ------------
63 |
64 | Required
65 | ++++++++
66 | - `geopandas`_
67 | - `seaborn`_
68 | - `descartes`_
69 | - `palettable`_
70 | - `scikit-learn`_
71 | - `rtree`_
72 | - `tqdm`_
73 | - `statsmodels`_
74 | - `deprecated`_
75 |
76 |
77 | Optional
78 | ++++++++
79 | - `urbanaccess`_
80 | - `pandana`_
81 | - `numba`_
82 | - `numexpr`_
83 | - `bokeh`_
84 |
85 |
86 |
87 |
88 | Installing versions supporting Python 2
89 | ---------------------------------------
90 |
91 | Users requiring Python 2 support can install the legacy version of PySAL: 1.1.14 via pip::
92 |
93 | pip install pysal==1.14.4.post2
94 |
95 | Note that this version is only receiving bug fixes. All new enhancements (post 2019-01) to PySAL are Python 3+ only, and are not available in 1.14.4.
96 |
97 | .. _3.7: https://docs.python.org/3.7/
98 | .. _3.6: https://docs.python.org/3.6/
99 | .. _Python Package Index: https://pypi.org/project/PySAL/
100 | .. _pysal/PySAL: https://github.com/pysal/PySAL
101 | .. _conda: https://docs.conda.io/en/latest/
102 | .. _Anaconda Distribution: https://docs.continuum.io/anaconda/
103 | .. _fork: https://help.github.com/articles/fork-a-repo/
104 | .. _geopandas: http://geopandas.org/install.html
105 | .. _seaborn: https://seaborn.pydata.org/installing.html
106 | .. _descartes: https://pypi.org/project/descartes/
107 | .. _palettable: https://jiffyclub.github.io/palettable/
108 | .. _scikit-learn: https://scikit-learn.org/stable/install.html
109 | .. _rtree: http://toblerity.org/rtree/install.html
110 | .. _tqdm: https://pypi.org/project/tqdm/
111 | .. _statsmodels: https://www.statsmodels.org/stable/install.html
112 | .. _deprecated: https://pypi.org/project/Deprecated/
113 | .. _urbanaccess: https://github.com/UDST/urbanaccess
114 | .. _pandana: https://pypi.org/project/pandana/
115 | .. _numba: https://numba.pydata.org/numba-doc/dev/user/installing.html
116 | .. _numexpr: https://pypi.org/project/numexpr/
117 | .. _bokeh: https://bokeh.pydata.org/en/latest/docs/installation.html
118 |
--------------------------------------------------------------------------------
/docs/references.rst:
--------------------------------------------------------------------------------
1 | .. reference for the docs
2 |
3 | References
4 | ==========
5 |
6 | .. bibliography:: _static/references.bib
7 | :cited:
8 |
--------------------------------------------------------------------------------
/docs/releases.rst:
--------------------------------------------------------------------------------
1 | PySAL Release Notes
2 | ===================
3 |
4 | This is the list of changes to PySAL for each release.
5 |
6 | - `PySAL 2.6.0 `_
7 | - `PySAL 2.5.0 `_
8 | - `PySAL 2.4.0 `_
9 | - `PySAL 2.3.0 `_
10 | - `PySAL 2.2.0 `_
11 | - `PySAL 2.1.0 `_
12 | - `PySAL 2.0.0 `_
13 | - `PySAL 1.14.4 `_
14 | - `PySAL 1.14.3 `_
15 | - `PySAL 1.14.2 `_
16 | - `PySAL 1.13.0 `_
17 | - `PySAL 1.12.0 `_
18 | - `PySAL 1.11.2 `_
19 | - `PySAL 1.11.1 `_
20 | - `PySAL 1.11.0 `_
21 | - `PySAL 1.10 `_
22 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: pysal
2 | channels:
3 | - conda-forge
4 | dependencies:
5 | - python>=3.10
6 | # ecosystem
7 | - access>=1.1.9
8 | - esda>=2.3.5
9 | - giddy>=2.3.5
10 | - inequality>=1.0.1
11 | - libpysal>=4.12.0
12 | - mapclassify>=2.7.0
13 | - mgwr>=2.2.1
14 | - momepy>=0.8.0
15 | - pointpats>=2.5.0
16 | - segregation>=2.5
17 | - spaghetti>=1.7.6
18 | - spglm>=1.1.0
19 | - spint>=1.0.7
20 | - splot>=1.1.5.post1
21 | - spopt>=0.6.1
22 | - spreg>=1.5.0
23 | - tobler>=0.11.3
24 | # externel
25 | - urllib3 >=1.26
26 |
--------------------------------------------------------------------------------
/figs/lisamaps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjsrey/pysal/e6db2d7dcd340e242459e8289578bfb4a4830298/figs/lisamaps.png
--------------------------------------------------------------------------------
/figs/lisamaps7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjsrey/pysal/e6db2d7dcd340e242459e8289578bfb4a4830298/figs/lisamaps7.png
--------------------------------------------------------------------------------
/figs/pysal_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjsrey/pysal/e6db2d7dcd340e242459e8289578bfb4a4830298/figs/pysal_logo.png
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [tool.setuptools_scm]
6 |
7 | [project]
8 | name = "pysal"
9 | dynamic = ["version"]
10 | authors = [ # in alphabetical order
11 | { name = "Serge Rey", email = "sjsrey@gmail.com" },
12 | { name = "Levi Wolf", email = "levi.john.wolf@gmail.com" },
13 | ]
14 | maintainers = [{ name = "PySAL Developers" }]
15 | license = { text = "BSD 3-Clause" }
16 | description = "Meta Package for PySAL - A library of spatial analysis functions"
17 | keywords = ["spatial statistics", "spatial graphs"]
18 | readme = "README.md"
19 | classifiers = [
20 | "Programming Language :: Python :: 3",
21 | "License :: OSI Approved :: BSD License",
22 | "Operating System :: OS Independent",
23 | "Intended Audience :: Science/Research",
24 | "Topic :: Scientific/Engineering :: GIS",
25 | ]
26 | requires-python = ">=3.10"
27 | dependencies = [
28 | "beautifulsoup4>=4.10",
29 | "geopandas>=0.10.0",
30 | "numpy>=1.22",
31 | "packaging>=22",
32 | "pandas>=1.4",
33 | "platformdirs>=2.0.2",
34 | "requests>=2.27",
35 | "scipy>=1.8",
36 | "shapely>=2.0.1",
37 | "scikit-learn>=1.1",
38 | "libpysal>=4.12.1",
39 | "access>=1.1.9",
40 | "esda>=2.6.0",
41 | "giddy>=2.3.6",
42 | "inequality>=1.1.1",
43 | "pointpats>=2.5.1",
44 | "segregation>=2.5.1",
45 | "spaghetti>=1.7.6",
46 | "mgwr>=2.2.1",
47 | "momepy>=0.9.1",
48 | "spglm>=1.1.0",
49 | "spint>=1.0.7",
50 | "spreg>=1.8.1",
51 | "tobler>=0.12.1",
52 | "mapclassify>=2.8.1",
53 | "splot>=1.1.7",
54 | "spopt>=0.6.1",
55 | ]
56 |
57 | [project.urls]
58 | Home = "https://github.com/pysal/pysal/"
59 | Repository = "https://github.com/pysal/pysal"
60 |
61 | [project.optional-dependencies]
62 | plus = [
63 | "joblib>=1.2",
64 | "networkx>=2.7",
65 | "numba>=0.55",
66 | "pyarrow>=7.0",
67 | "sqlalchemy>=2.0",
68 | "xarray>=2022.3",
69 | "zstd",
70 | ]
71 | dev = [
72 | "pre-commit",
73 | "ruff",
74 | "watermark",
75 | ]
76 | docs = [
77 | "mkdocs-jupyter",
78 | "myst-parser",
79 | "nbsphinx",
80 | "numpydoc",
81 | "pandoc",
82 | "sphinx",
83 | 'sphinx_design',
84 | "sphinxcontrib-bibtex",
85 | "sphinx_pydata_theme",
86 | ]
87 | tests = [
88 | "codecov",
89 | "geodatasets>=2023.3.0",
90 | "matplotlib>=3.6",
91 | "pytest",
92 | "pytest-mpl",
93 | "pytest-cov",
94 | "pytest-xdist",
95 | ]
96 |
97 | [tool.setuptools.packages.find]
98 | include = ["pysal", "pysal.*"]
99 |
100 | [tool.ruff]
101 | line-length = 88
102 | lint.select = ["E", "F", "W", "I", "UP", "N", "B", "A", "C4", "SIM", "ARG"]
103 | target-version = "py310"
104 | exclude = ["pysal/tests/*", "docs/*"]
105 | [tool.ruff.lint.per-file-ignores]
106 | "*__init__.py" = [
107 | "F401", # imported but unused
108 | "F403", # star import; unable to detect undefined names
109 | ]
110 |
111 |
112 | [tool.coverage.run]
113 | source = ["./pysal"]
114 |
115 | [tool.coverage.report]
116 | exclude_lines = [
117 | "raise NotImplementedError",
118 | "except ModuleNotFoundError:",
119 | "except ImportError",
120 | ]
121 | ignore_errors = true
122 | omit = ["pysal/tests/*", "docs/conf.py"]
123 |
124 | [tool.pytest.ini_options]
125 | filterwarnings = [
126 | "ignore:The numba package is used",
127 | "ignore:numba cannot be imported",
128 | "ignore:Numba not imported",
129 | "ignore:The weights matrix is not fully connected",
130 | "ignore:You are trying to build a full W object from",
131 | "ignore:Multiple layers detected. Using first layer as default",
132 | "ignore:Geometry is in a geographic CRS",
133 | "ignore:`use_index` defaults to False",
134 | "ignore:Objects based on the `Geometry` class will deprecated",
135 | "ignore:PolygonLocator is deprecated",
136 | "ignore:SegmentGrid is deprecated",
137 | "ignore:In the next version of pysal, observations with no neighbors",
138 | "ignore:divide by zero encountered",
139 | "ignore:invalid value encountered",
140 | "ignore:Passing a SingleBlockManager", # https://github.com/geopandas/geopandas/issues/3060
141 | "ignore:Passing a BlockManager", # https://github.com/geopandas/geopandas/issues/3060
142 | ]
143 |
--------------------------------------------------------------------------------
/pysal/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | PySAL: Python Spatial Analysis Library
3 | ======================================
4 |
5 | A federation of packages for spatial data science.
6 |
7 |
8 | Layers and Subpackages
9 | ----------------------
10 | PySAL is organized into four layers (lib, explore, model, and viz), each of which contains subpackages for a particular type of spatial data analysis.
11 |
12 |
13 | Use of any of these layers requires an explicit import. For example,
14 | ``from pysal.explore import esda``
15 |
16 | lib: core algorithms, weights, and spatial data structures
17 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 |
19 | cg -- Computational Geometry
20 | examples -- Example data sets
21 | graph -– Graph class encoding spatial weights matrices
22 | io -- Input/Output
23 | weights -- Spatial Weights
24 |
25 |
26 | explore: exploratory spatial data analysis
27 | ++++++++++++++++++++++++++++++++++++++++++
28 |
29 | esda -- Global and local spatial autocorrelation
30 | giddy -- Spatial distribution dynamics
31 | inequality -- Spatial inequality measures
32 | pointpats -- Planar point pattern analysis
33 | segregation -- Segregation analytics
34 | spaghetti -- Spatial analysis on networks
35 |
36 |
37 | model: spatial statistical models
38 | +++++++++++++++++++++++++++++++++
39 |
40 | access -- Measures of spatial accessibility
41 | mgwr -- Multi-scale geographically weighted regression
42 | spint -- Spatial interaction modeling
43 | spglm -- Spatial general linear modeling
44 | spopt -- Spatial optimization
45 | spreg -- Spatial econometrics
46 | tobler -- Spatial areal interpolation models
47 |
48 |
49 | viz: geovisualization
50 | +++++++++++++++++++++
51 |
52 | mapclassify -- Classification schemes for choropleth maps
53 | splot -- Geovisualization for pysal
54 |
55 | """
56 | from .base import memberships, federation_hierarchy
57 |
58 | import contextlib
59 | from importlib.metadata import PackageNotFoundError, version
60 |
61 |
62 | with contextlib.suppress(PackageNotFoundError):
63 | __version__ = version("pysal")
64 |
--------------------------------------------------------------------------------
/pysal/base.py:
--------------------------------------------------------------------------------
1 | """
2 | Base information for pysal meta package
3 | """
4 |
5 |
6 | federation_hierarchy = {
7 | 'explore': ['esda', 'giddy', 'segregation',
8 | 'pointpats', 'inequality',
9 | 'spaghetti', 'access', 'momepy'],
10 | 'model': ['spreg', 'spglm', 'tobler', 'spint',
11 | 'mgwr', 'access', 'spopt'],
12 | 'viz': ['splot', 'mapclassify'],
13 | 'lib': ['libpysal']
14 | }
15 |
16 | memberships = {}
17 | for key in federation_hierarchy:
18 | for package in federation_hierarchy[key]:
19 | memberships[package] = key
20 |
21 |
22 |
23 | class cached_property(object):
24 | """ A property that is only computed once per instance and then replaces
25 | itself with an ordinary attribute. Deleting the attribute resets the
26 | property.
27 |
28 | Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
29 | """
30 |
31 | def __init__(self, func):
32 | self.__doc__ = getattr(func, '__doc__')
33 | self.func = func
34 |
35 | def __get__(self, obj, cls):
36 | if obj is None:
37 | return self
38 | value = obj.__dict__[self.func.__name__] = self.func(obj)
39 | return value
40 |
41 | def _installed_version(package):
42 | try:
43 | exec(f'import {package}')
44 | except ModuleNotFoundError:
45 | v = 'NA'
46 | try:
47 | v = eval(f'{package}.__version__')
48 | except AttributeError:
49 | v = 'NA'
50 | return v
51 |
52 | def _installed_versions():
53 | ver = {}
54 | for package in memberships.keys():
55 | ver[package] = _installed_version(package)
56 | return ver
57 |
58 | def _released_versions():
59 | from .frozen import frozen_packages
60 | return frozen_packages
61 |
62 |
63 | class Versions:
64 | @cached_property
65 | def installed(self):
66 | """
67 | Inventory versions of pysal packages that are installed
68 |
69 | Attributes
70 | ----------
71 | installed: dict
72 | key is package name, value is version string
73 | """
74 | return _installed_versions()
75 |
76 | @cached_property
77 | def released(self):
78 | """
79 | Inventory versions of pysal packages that are released in the meta
80 | package.
81 |
82 | Attributes
83 | ----------
84 | released: dict
85 | key is package name, value is version string
86 | """
87 |
88 | return _released_versions()
89 |
90 | def check(self):
91 | """
92 | Print a tabular string that reports installed and released versions of
93 | PySAL packages.
94 | """
95 | table = []
96 | package = "Package"
97 | installed = "Installed"
98 | released = "Released"
99 | match = "Match"
100 | s = f'{package:>12} | {installed:>15} | {released:>15} | {match:>5}'
101 | table.append(s)
102 | table.append("-"*len(s))
103 | for package in self.installed:
104 | installed = self.installed[package]
105 | released = self.released[package]
106 | match = installed == released
107 | s = f'{package:>12} | {installed:>15} | {released:>15} | {match:>5}'
108 | table.append(s)
109 | print("\n".join(table))
110 |
111 |
112 | versions = Versions()
113 |
114 |
--------------------------------------------------------------------------------
/pysal/explore/__init__.py:
--------------------------------------------------------------------------------
1 | import esda
2 | import giddy
3 | import inequality
4 | import pointpats
5 | import spaghetti
6 | import segregation
7 |
--------------------------------------------------------------------------------
/pysal/explore/esda/__init__.py:
--------------------------------------------------------------------------------
1 | from esda.moran import (
2 | Moran,
3 | Moran_BV,
4 | Moran_BV_matrix,
5 | Moran_Local,
6 | Moran_Local_BV,
7 | Moran_Rate,
8 | Moran_Local_Rate,
9 | )
10 | from esda.getisord import G, G_Local
11 | from esda.geary import Geary
12 | from esda.join_counts import Join_Counts
13 | from esda.gamma import Gamma
14 | from esda.util import fdr
15 | from esda.smaup import Smaup
16 | from esda.lee import Spatial_Pearson, Local_Spatial_Pearson
17 | from esda.silhouettes import (path_silhouette, boundary_silhouette,
18 | silhouette_alist, nearest_label)
19 |
--------------------------------------------------------------------------------
/pysal/explore/giddy/__init__.py:
--------------------------------------------------------------------------------
1 | from giddy import directional
2 | from giddy import ergodic
3 | from giddy import markov
4 | from giddy import mobility
5 | from giddy import rank
6 | from giddy import util
7 | from giddy import sequence
8 |
--------------------------------------------------------------------------------
/pysal/explore/inequality/__init__.py:
--------------------------------------------------------------------------------
1 | from inequality import theil
2 | from inequality import gini
3 |
--------------------------------------------------------------------------------
/pysal/explore/momepy/__init__.py:
--------------------------------------------------------------------------------
1 | from momepy import *
2 |
--------------------------------------------------------------------------------
/pysal/explore/pointpats/__init__.py:
--------------------------------------------------------------------------------
1 | from pointpats.pointpattern import PointPattern
2 | from pointpats.window import as_window, poly_from_bbox, to_ccf, Window
3 | from pointpats.centrography import *
4 | from pointpats.process import *
5 | from pointpats.quadrat_statistics import *
6 | from pointpats.distance_statistics import *
7 |
--------------------------------------------------------------------------------
/pysal/explore/segregation/__init__.py:
--------------------------------------------------------------------------------
1 | from segregation import aspatial
2 | from segregation import spatial
3 | from segregation import inference
4 | from segregation import decomposition
5 | from segregation import util
6 | from segregation import network
7 | from segregation import local
8 | from segregation import compute_all
9 |
--------------------------------------------------------------------------------
/pysal/explore/spaghetti/__init__.py:
--------------------------------------------------------------------------------
1 | from spaghetti.network import Network, PointPattern, SimulatedPointPattern
2 | from spaghetti.network import element_as_gdf
3 |
--------------------------------------------------------------------------------
/pysal/lib/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "4.2.1"
2 |
3 | # __version__ has to be define in the first line
4 |
5 | """
6 | pysal.lib: Python Spatial Analysis Library (core)
7 | ================================================
8 |
9 |
10 | Documentation
11 | -------------
12 | PySAL documentation is available in two forms: python docstrings and an html \
13 | webpage at http://pysal.org/
14 |
15 | Available sub-packages
16 | ----------------------
17 |
18 | cg
19 | Basic data structures and tools for Computational Geometry
20 | examples
21 | Example data sets for testing and documentation
22 | io
23 | Basic functions used by several sub-packages
24 | weights
25 | Tools for creating and manipulating weights
26 | """
27 |
28 | from libpysal import cg
29 | from libpysal import io
30 | from libpysal import weights
31 | from libpysal import examples
32 |
33 |
--------------------------------------------------------------------------------
/pysal/lib/common.py:
--------------------------------------------------------------------------------
1 | import copy
2 | import sys
3 | import time
4 |
5 | # external imports
6 | import numpy as np
7 | import numpy.linalg as la
8 |
9 | import scipy as sp
10 | import scipy.stats as stats
11 | from libpysal.cg.kdtree import KDTree
12 | from scipy.spatial.distance import pdist, cdist
13 |
14 | import pandas
15 |
16 | try:
17 | from patsy import PatsyError
18 | except ImportError:
19 | PatsyError = Exception
20 |
21 | RTOL = .00001
22 | ATOL = 1e-7
23 | MISSINGVALUE = None
24 |
25 | ######################
26 | # Decorators/Utils #
27 | ######################
28 |
29 | # import numba.jit OR create mimic decorator and set existence flag
30 | try:
31 | from numba import jit
32 | HAS_JIT = True
33 | except ImportError:
34 | def jit(function=None, **kwargs):
35 | """Mimic numba.jit() with synthetic wrapper
36 | """
37 | if function is not None:
38 | def wrapped(*original_args, **original_kw):
39 | """Case 1 - structure of a standard decorator
40 | i.e., jit(function)(*args, **kwargs)
41 | """
42 | return function(*original_args, **original_kw)
43 | return wrapped
44 | else:
45 | def partial_inner(func):
46 | """Case 2 - returns Case 1
47 | i.e., jit()(function)(*args, **kwargs)
48 | """
49 | return jit(func)
50 | return partial_inner
51 | HAS_JIT = False
52 |
53 | def simport(modname):
54 | """
55 | Safely import a module without raising an error.
56 |
57 | Parameters
58 | -----------
59 | modname : str
60 | module name needed to import
61 |
62 | Returns
63 | --------
64 | tuple of (True, Module) or (False, None) depending on whether the import
65 | succeeded.
66 |
67 | Notes
68 | ------
69 | Wrapping this function around an iterative context or a with context would
70 | allow the module to be used without necessarily attaching it permanently in
71 | the global namespace:
72 |
73 |
74 | for t,mod in simport('pandas'):
75 | if t:
76 | mod.DataFrame()
77 | else:
78 | #do alternative behavior here
79 | del mod #or don't del, your call
80 |
81 | instead of:
82 |
83 | t, mod = simport('pandas')
84 | if t:
85 | mod.DataFrame()
86 | else:
87 | #do alternative behavior here
88 |
89 | The first idiom makes it work kind of a like a with statement.
90 | """
91 | try:
92 | exec('import {}'.format(modname))
93 | return True, eval(modname)
94 | except:
95 | return False, None
96 |
97 | def requires(*args, **kwargs):
98 | """
99 | Decorator to wrap functions with extra dependencies:
100 |
101 | Arguments
102 | ---------
103 | args : list
104 | list of strings containing module to import
105 | verbose : bool
106 | boolean describing whether to print a warning message on import
107 | failure
108 | Returns
109 | -------
110 | Original function is all arg in args are importable, otherwise returns a
111 | function that passes.
112 | """
113 | v = kwargs.pop('verbose', True)
114 | wanted = copy.deepcopy(args)
115 | def inner(function):
116 | available = [simport(arg)[0] for arg in args]
117 | if all(available):
118 | return function
119 | else:
120 | def passer(*args,**kwargs):
121 | if v:
122 | missing = [arg for i, arg in enumerate(wanted) if not available[i]]
123 | print(('missing dependencies: {d}'.format(d=missing)))
124 | print(('not running {}'.format(function.__name__)))
125 | else:
126 | pass
127 | return passer
128 | return inner
129 |
--------------------------------------------------------------------------------
/pysal/lib/examples/__init__.py:
--------------------------------------------------------------------------------
1 | from libpysal.examples import (get_path, available,
2 | load_example, explain)
3 |
--------------------------------------------------------------------------------
/pysal/model/__init__.py:
--------------------------------------------------------------------------------
1 | import access
2 | import mgwr
3 | import spglm
4 | import spint
5 | import spreg
6 | import tobler
7 | import spopt
8 |
--------------------------------------------------------------------------------
/pysal/model/access/__init__.py:
--------------------------------------------------------------------------------
1 | from access import fca
2 | from access import raam
3 | from access import weights
4 | from access import helpers
5 | from access.datasets import datasets
6 | from access import access
7 |
--------------------------------------------------------------------------------
/pysal/model/mgwr/__init__.py:
--------------------------------------------------------------------------------
1 | from mgwr import gwr
2 | from mgwr import sel_bw
3 | from mgwr import diagnostics
4 | from mgwr import kernels
5 |
--------------------------------------------------------------------------------
/pysal/model/spglm/__init__.py:
--------------------------------------------------------------------------------
1 | from spglm import glm
2 | from spglm import family
3 | from spglm import utils
4 | from spglm import iwls
5 |
--------------------------------------------------------------------------------
/pysal/model/spint/__init__.py:
--------------------------------------------------------------------------------
1 | from spint.gravity import Gravity, Production, Attraction, Doubly
2 | from spint.utils import CPC, sorensen, srmse
3 | from spint.vec_SA import VecMoran as Moran_Vector
4 | from spint.dispersion import phi_disp, alpha_disp
5 |
--------------------------------------------------------------------------------
/pysal/model/spopt/__init__.py:
--------------------------------------------------------------------------------
1 | from spopt import region
2 |
--------------------------------------------------------------------------------
/pysal/model/spreg/__init__.py:
--------------------------------------------------------------------------------
1 | from spreg.ols import *
2 | from spreg.diagnostics import *
3 | from spreg.diagnostics_sp import *
4 | from spreg.diagnostics_sur import *
5 | from spreg.diagnostics_tsls import *
6 | from spreg.user_output import *
7 | from spreg.twosls import *
8 | from spreg.twosls_sp import *
9 | from spreg.error_sp import *
10 | from spreg.error_sp_het import *
11 | from spreg.error_sp_hom import *
12 | from spreg.ols_regimes import *
13 | from spreg.twosls_regimes import *
14 | from spreg.twosls_sp_regimes import *
15 | from spreg.error_sp_regimes import *
16 | from spreg.error_sp_het_regimes import *
17 | from spreg.error_sp_hom_regimes import *
18 | from spreg.probit import *
19 | from spreg.ml_lag import *
20 | from spreg.ml_lag_regimes import *
21 | from spreg.ml_error import *
22 | from spreg.ml_error_regimes import *
23 | from spreg.sur import *
24 | from spreg.sur_error import *
25 | from spreg.sur_lag import *
26 | from spreg.sur_utils import *
27 | from spreg.utils import *
28 | from spreg.regimes import *
29 | from spreg.sputils import *
30 |
--------------------------------------------------------------------------------
/pysal/model/tobler/__init__.py:
--------------------------------------------------------------------------------
1 | from tobler import area_weighted
2 | from tobler import dasymetric
3 | from tobler import model
4 |
--------------------------------------------------------------------------------
/pysal/tests/test_imports.py:
--------------------------------------------------------------------------------
1 | from pkgutil import iter_modules
2 | from pysal import federation_hierarchy
3 |
4 |
5 | def module_exists(module_name):
6 | return module_name in (name for loader, name, ispkg in iter_modules())
7 |
8 | def test_imports():
9 | for layer in federation_hierarchy:
10 | packages = federation_hierarchy[layer]
11 | for package in packages:
12 | assert module_exists(package), f"{package} not installed."
13 |
--------------------------------------------------------------------------------
/pysal/viz/__init__.py:
--------------------------------------------------------------------------------
1 | import mapclassify
2 | import splot
3 |
--------------------------------------------------------------------------------
/pysal/viz/mapclassify/__init__.py:
--------------------------------------------------------------------------------
1 | from mapclassify.classifiers import (
2 | BoxPlot,
3 | EqualInterval,
4 | FisherJenks,
5 | FisherJenksSampled,
6 | HeadTailBreaks,
7 | JenksCaspall,
8 | JenksCaspallForced,
9 | JenksCaspallSampled,
10 | MaxP,
11 | MaximumBreaks,
12 | NaturalBreaks,
13 | Quantiles,
14 | Percentiles,
15 | StdMean,
16 | UserDefined,
17 | load_example,
18 | gadf,
19 | KClassifiers,
20 | CLASSIFIERS,
21 | )
22 |
--------------------------------------------------------------------------------
/tools/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile
2 |
3 | # Define the default target that runs all commands in sequence
4 | .PHONY: all
5 | all: frozen gitcount changelog
6 |
7 | # Define individual commands
8 | .PHONY: frozen
9 | frozen:
10 | @echo "Running frozen.py..."
11 | python frozen.py
12 |
13 | .PHONY: gitcount
14 | gitcount:
15 | @echo "Running gitcount.py..."
16 | python gitcount.py
17 |
18 | .PHONY: changelog
19 | changelog:
20 | @echo "Running change_log.py..."
21 | python change_log.py
22 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Tooling to Build PySAL Meta Package
2 |
3 | ## Dependencies
4 |
5 | - [personal github token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
6 | ): store it in the file `token`
7 |
8 | ## Instructions
9 |
10 | ### Updating package information
11 | - If any new packages have been added to the ecosystem update the `packages` list in `release.yaml`
12 | - Update relevant data on `start_date` (day after last release), `release_date` (day
13 | of this release), `version`, and `user` in `release.yaml`
14 |
15 | If this is a release candidate, do not start the `version` string with `v` but
16 | do add `rcX` ad the end of the string, where `X` is the number for the current
17 | release candidate.
18 |
19 | If this a production release, the first character in `version` needs to b `v`
20 | to ensure the publish and release workflow is run in the CI.
21 |
22 |
23 |
24 | ### Updating the changelog
25 | - `make` will run all the steps required to build the new change log
26 |
27 | For debugging purposes, the individual steps can be run using:
28 | - `make frozen` will get information about latest package releases
29 | - `make gitcount` will get issues and pulls closed for each package
30 | - `make changelog` will update the change log and write to `changes.md`
31 |
32 | These require `release_info.py`
33 |
34 | ### Add and Commit
35 | - `git add ../pyproject.toml`
36 | - `git add release.yaml`
37 | - `git commit -m "REL: "`
38 |
39 |
40 | ### Create a tag and push upstream
41 | - `git tag `
42 | - `git push upstream `
43 |
44 |
45 | ### Updating meta package release notes
46 | - edit the file `changelog.md` and incorporate into the release notes on github
47 |
--------------------------------------------------------------------------------
/tools/change_log.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # ## PySAL Change Log Statistics: Table Generation
5 |
6 | from __future__ import print_function
7 | from collections import Counter
8 | from datetime import date, datetime, time
9 | from datetime import datetime
10 | from release_info import get_pypi_info, get_github_info, clone_masters
11 | import pickle
12 | import release_info
13 | from release_info import release_date, start_date, PYSALVER
14 | import os
15 | import json
16 | import re
17 | import sys
18 | import pandas
19 | import subprocess
20 | from subprocess import check_output
21 |
22 | # import yaml
23 | from datetime import datetime, timedelta, time
24 |
25 | from dateutil.parser import parse
26 | import pytz
27 |
28 | utc = pytz.UTC
29 |
30 | try:
31 | from urllib import urlopen
32 | except:
33 | from urllib.request import urlopen
34 |
35 | since = datetime.combine(start_date, time(0, 0))
36 | CWD = os.path.abspath(os.path.curdir)
37 |
38 | with open('frozen.txt', 'r') as package_list:
39 | packages = package_list.readlines()
40 | packages = dict([package.strip().split(">=") for package in packages])
41 |
42 | packages['pysal'] = release_info.PYSALVER
43 | issues_closed = pickle.load(open("issues_closed.p", 'rb'))
44 | pulls_closed = pickle.load(open('pulls_closed.p', 'rb'))
45 | github_releases = pickle.load(open("releases.p", 'rb'))
46 | ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
47 | pysal_date = release_date
48 |
49 | # Create a date object
50 | date_obj = release_date
51 | # Create a time object (optional, default is midnight if not specified)
52 | time_obj = time(0, 0)
53 | # Combine date and time to create a datetime object
54 | datetime_obj = datetime.combine(date_obj, time_obj)
55 | pysal_rel = {'version': f'v{PYSALVER}',
56 | 'release_date': datetime_obj}
57 | github_releases['pysal'] = pysal_rel
58 |
59 |
60 | final_pulls = {}
61 | final_issues = {}
62 | for package in packages:
63 | filtered_issues = []
64 | filtered_pulls = []
65 | released = github_releases[package]['release_date']
66 | package_pulls = pulls_closed[package]
67 | package_issues = issues_closed[package]
68 | for issue in package_issues:
69 | # print(issue['number'], issue['title'], issue['closed_at'])
70 | closed = datetime.strptime(issue['closed_at'], ISO8601)
71 | if closed <= released and closed > since:
72 | filtered_issues.append(issue)
73 | final_issues[package] = filtered_issues
74 | for pull in package_pulls:
75 | # print(pull['number'], pull['title'], pull['closed_at'])
76 | closed = datetime.strptime(pull['closed_at'], ISO8601)
77 | if closed <= released and closed > since:
78 | filtered_pulls.append(pull)
79 | final_pulls[package] = filtered_pulls
80 |
81 | issue_details = final_issues
82 | pull_details = final_pulls
83 | github_releases['pysal']['release_date'] = release_date
84 |
85 |
86 | # skip packages not released since last meta release
87 | # handle meta
88 | mrd = github_releases['pysal']['release_date']
89 | github_releases['pysal']['release_date'] = datetime.combine(mrd, time(0, 0))
90 |
91 | for package in github_releases:
92 | if github_releases[package]['release_date'] > since:
93 | print("new: ", package)
94 | else:
95 | print('old:', package)
96 |
97 | since_date = '--since="{start}"'.format(start=start_date.strftime("%Y-%m-%d"))
98 |
99 | # commits
100 | cmd = ['git', 'log', '--oneline', since_date]
101 |
102 | activity = {}
103 | total_commits = 0
104 | tag_dates = {}
105 | ncommits_total = 0
106 | for subpackage in packages:
107 | released = github_releases[subpackage]['release_date']
108 | tag_date = released.strftime("%Y-%m-%d")
109 | tag_dates[subpackage] = tag_date
110 | print(tag_date)
111 | # tag_date = tag_dates[subpackage]
112 | ncommits = 0
113 | if released > since:
114 | os.chdir(CWD)
115 | os.chdir('tmp/{subpackage}'.format(subpackage=subpackage))
116 | cmd_until = cmd + ['--until="{tag_date}"'.format(tag_date=tag_date)]
117 | ncommits = len(check_output(cmd_until).splitlines())
118 | ncommits_total = len(check_output(cmd).splitlines())
119 | print(subpackage, ncommits_total, ncommits, tag_date)
120 | total_commits += ncommits
121 | activity[subpackage] = ncommits
122 |
123 |
124 | cmd = ['git', 'log', '--oneline', since_date]
125 |
126 | activity = {}
127 | total_commits = 0
128 | for subpackage in packages:
129 | ncommits = 0
130 | tag_date = tag_dates[subpackage]
131 | released = github_releases[subpackage]['release_date']
132 | if released > since:
133 | os.chdir(CWD)
134 | os.chdir('tmp/{subpackage}'.format(subpackage=subpackage))
135 | cmd_until = cmd + ['--until="{tag_date}"'.format(tag_date=tag_date)]
136 | ncommits = len(check_output(cmd_until).splitlines())
137 | print(ncommits)
138 | ncommits_total = len(check_output(cmd).splitlines())
139 | print(subpackage, ncommits_total, ncommits, tag_date)
140 | total_commits += ncommits
141 | activity[subpackage] = ncommits
142 |
143 |
144 | identities = {'Levi John Wolf': ('ljwolf', 'Levi John Wolf'),
145 | 'Serge Rey': ('Serge Rey', 'Sergio Rey', 'sjsrey', 'serge'),
146 | 'Wei Kang': ('Wei Kang', 'weikang9009'),
147 | 'Dani Arribas-Bel': ('Dani Arribas-Bel', 'darribas'),
148 | 'Antti Härkönen': ('antth', 'Antti Härkönen', 'Antti Härkönen', 'Antth'),
149 | 'Juan C Duque': ('Juan C Duque', "Juan Duque"),
150 | 'Renan Xavier Cortes': ('Renan Xavier Cortes', 'renanxcortes', 'Renan Xavier Cortes'),
151 | 'Taylor Oshan': ('Tayloroshan', 'Taylor Oshan', 'TaylorOshan'),
152 | 'Tom Gertin': ('@Tomgertin', 'Tom Gertin', '@tomgertin')
153 | }
154 |
155 |
156 | def regularize_identity(string):
157 | string = string.decode()
158 | for name, aliases in identities.items():
159 | for alias in aliases:
160 | if alias in string:
161 | string = string.replace(alias, name)
162 | if len(string.split(' ')) > 1:
163 | string = string.title()
164 | return string.lstrip('* ')
165 |
166 |
167 | author_cmd = ['git', 'log', '--format=* %aN', since_date]
168 |
169 |
170 | author_cmd.append('blank')
171 |
172 |
173 | authors_global = set()
174 | authors = {}
175 | global_counter = Counter()
176 | counters = dict()
177 | cmd = ['git', 'log', '--oneline', since_date]
178 | total_commits = 0
179 | activity = {}
180 | for subpackage in packages:
181 | ncommits = 0
182 | released = github_releases[subpackage]['release_date']
183 | if released > since:
184 | os.chdir(CWD)
185 | os.chdir('tmp/{subpackage}'.format(subpackage=subpackage))
186 | ncommits = len(check_output(cmd).splitlines())
187 | tag_date = tag_dates[subpackage]
188 | tag_date = (datetime.strptime(tag_date, '%Y-%m-%d') +
189 | timedelta(days=1)).strftime('%Y-%m-%d')
190 | author_cmd[-1] = '--until="{tag_date}"'.format(tag_date=tag_date)
191 | # cmd_until = cmd + ['--until="{tag_date}"'.format(tag_date=tag_date)]
192 | print(subpackage, author_cmd)
193 |
194 | all_authors = check_output(author_cmd).splitlines()
195 | counter = Counter([regularize_identity(author)
196 | for author in all_authors])
197 | global_counter += counter
198 | counters.update({subpackage: counter})
199 | unique_authors = sorted(set(all_authors))
200 | authors[subpackage] = unique_authors
201 | authors_global.update(unique_authors)
202 | total_commits += ncommits
203 | activity[subpackage] = ncommits
204 |
205 |
206 | def get_tag(title, level="##", as_string=True):
207 | words = title.split()
208 | tag = "-".join([word.lower() for word in words])
209 | heading = level+" "+title
210 | line = "\n\n".format(tag)
211 | lines = [line]
212 | lines.append(heading)
213 | if as_string:
214 | return "\n".join(lines)
215 | else:
216 | return lines
217 |
218 |
219 | subs = issue_details.keys()
220 | table = []
221 | txt = []
222 | lines = get_tag("Changes by Package", as_string=False)
223 |
224 | for sub in github_releases:
225 | total = issue_details[sub]
226 | pr = pull_details[sub]
227 |
228 | row = [sub, activity[sub], len(total), len(pr)]
229 | table.append(row)
230 | # line = "\n".format(sub=sub)
231 | # lines.append(line)
232 | # line = "### {sub}".format(sub=sub)
233 | # lines.append(line)
234 | sub_lower = sub.lower()
235 | sub_version = github_releases[sub_lower]['version']
236 | print(f'{sub_lower}, {sub_version}')
237 | title = f'{sub_lower} {sub_version}'
238 | lines.extend(get_tag(title, "###", as_string=False))
239 | for issue in total:
240 | url = issue['html_url']
241 | title = issue['title']
242 | number = issue['number']
243 | line = "* [#{number}:]({url}) {title} ".format(title=title,
244 | number=number,
245 | url=url)
246 | lines.append(line)
247 |
248 |
249 | os.chdir(CWD)
250 |
251 | df = pandas.DataFrame(
252 | table, columns=['package', 'commits', 'total issues', 'pulls'])
253 |
254 | df.sort_values(['commits', 'pulls'], ascending=False)\
255 | .to_html('./commit_table.html', index=None)
256 |
257 | contributor_table = pandas.DataFrame.from_dict(
258 | counters).fillna(0).astype(int).T
259 |
260 | contributor_table.to_html('./contributor_table.html')
261 |
262 | totals = contributor_table.sum(axis=0).T
263 | totals.sort_index().to_frame('commits')
264 |
265 | totals = contributor_table.sum(axis=0).T
266 | totals.sort_index().to_frame('commits').to_html('./commits_by_person.html')
267 |
268 | n_commits = df.commits.sum()
269 | n_issues = df['total issues'].sum()
270 | n_pulls = df.pulls.sum()
271 |
272 | line = ('Overall, there were {n_commits} commits that closed {n_issues} issues'
273 | ' since our last release'
274 | ' on {since_date}.\n'.format(n_commits=n_commits, n_issues=n_issues,
275 | since_date=start_date))
276 |
277 |
278 | with open('changelog.md', 'w') as fout:
279 | fout.write(line)
280 | fout.write("\n".join(lines))
281 | fout.write(get_tag("Contributors"))
282 | fout.write(
283 | "\n\nMany thanks to all of the following individuals who contributed to this release:\n\n")
284 |
285 | totals = contributor_table.sum(axis=0).T
286 | contributors = totals.index.values
287 | contributors.sort()
288 | contributors = contributors.tolist()
289 | contributors = [f'\n - {contributor}' for contributor in contributors]
290 | fout.write("".join(contributors))
291 |
292 |
293 | df.head()
294 |
295 |
296 | # Update ../pyproject.toml for minimum pysal package pinning
297 | # get version numbers from frozen.txt
298 | with open('frozen.txt', 'r') as frozen:
299 | packages = [line.rstrip() for line in frozen.readlines()]
300 |
301 | # search pyproject.toml for lines containing package
302 | with open('../pyproject.toml', 'r') as project:
303 | lines = [line.rstrip() for line in project.readlines()]
304 |
305 | # split line ->" package", ">=", "version",
306 | # replace version and rebuild line to update
307 | for package in packages:
308 | name, version = package.split(">=")
309 | i, match = [(i, line) for i, line in enumerate(lines) if name in line][0]
310 | old_name, old_version = match.split(">=")
311 | new_line = ">=".join([old_name, version+'",'])
312 | lines[i] = new_line
313 |
314 | # write out new pyproject.toml file
315 | with open("../pyproject.toml", 'w') as output:
316 | output.write("\n".join(lines))
317 |
--------------------------------------------------------------------------------
/tools/changelog.md:
--------------------------------------------------------------------------------
1 | Overall, there were 302 commits that closed 122 issues since our last release on 2024-07-31.
2 |
3 |
4 |
5 | ## Changes by Package
6 |
7 |
8 |
9 | ### libpysal v4.12.1
10 | * [#765:](https://github.com/pysal/libpysal/pull/765) ENH: ensure lag_spatial is compatible with both W and Graph
11 | * [#761:](https://github.com/pysal/libpysal/pull/761) Add exponential kernel to Graph
12 | * [#763:](https://github.com/pysal/libpysal/pull/763) allow continuous weights for knn graph
13 | * [#760:](https://github.com/pysal/libpysal/pull/760) Fix for Graph.describe() when the graph has a string index (#759)
14 | * [#759:](https://github.com/pysal/libpysal/issues/759) BUG: Graph.describe() does not work with non-integer index
15 |
16 |
17 |
18 | ### giddy v2.3.6
19 | * [#228:](https://github.com/pysal/giddy/pull/228) update CI - minimum versions, naming, etc.
20 | * [#229:](https://github.com/pysal/giddy/issues/229) some linting adjustments [2024-07-15]
21 | * [#227:](https://github.com/pysal/giddy/issues/227) update chat from `gitter` to `discord`
22 | * [#226:](https://github.com/pysal/giddy/issues/226) drop 3.9 as minimally support Python version?
23 | * [#225:](https://github.com/pysal/giddy/issues/225) CI maint for standard naming, oldest dependencies, & Python 3.12
24 |
25 |
26 |
27 | ### inequality v1.1.1
28 | * [#96:](https://github.com/pysal/inequality/pull/96) move conftest
29 | * [#97:](https://github.com/pysal/inequality/pull/97) remove print statements
30 | * [#95:](https://github.com/pysal/inequality/pull/95) Correct sphinx theme
31 | * [#94:](https://github.com/pysal/inequality/pull/94) Fix theme for doc build
32 | * [#93:](https://github.com/pysal/inequality/pull/93) Fix inconsistency in efficient gini
33 | * [#16:](https://github.com/pysal/inequality/issues/16) Inconsistent results with different input shape for Spatial_Gini
34 | * [#92:](https://github.com/pysal/inequality/pull/92) Documentation and landing page updates
35 | * [#15:](https://github.com/pysal/inequality/issues/15) TheilD within group inequality
36 | * [#91:](https://github.com/pysal/inequality/pull/91) [pre-commit.ci] pre-commit autoupdate
37 | * [#90:](https://github.com/pysal/inequality/pull/90) Theil doc
38 | * [#89:](https://github.com/pysal/inequality/pull/89) wolfson nb narrative
39 | * [#88:](https://github.com/pysal/inequality/pull/88) Polarization indices and new documentation
40 | * [#86:](https://github.com/pysal/inequality/pull/86) added narrative to theil nb
41 | * [#87:](https://github.com/pysal/inequality/pull/87) Gini polarization
42 | * [#85:](https://github.com/pysal/inequality/pull/85) [pre-commit.ci] pre-commit autoupdate
43 | * [#84:](https://github.com/pysal/inequality/pull/84) remove [Nijkamp & Poot (2013)] citation
44 | * [#36:](https://github.com/pysal/inequality/issues/36) Nijkamp & Poot (2013)?
45 | * [#82:](https://github.com/pysal/inequality/issues/82) add root-level `conftest.py` to skip doctest only in `_indices.py`
46 | * [#81:](https://github.com/pysal/inequality/issues/81) adjust tests for warnings
47 | * [#83:](https://github.com/pysal/inequality/pull/83) resolve all warnings from CI -- [2024-09-02]
48 | * [#80:](https://github.com/pysal/inequality/pull/80) add matplotlib as a requirement
49 | * [#79:](https://github.com/pysal/inequality/issues/79) add `matplotlib` as a dependency
50 | * [#75:](https://github.com/pysal/inequality/pull/75) Schutz inequality measures
51 | * [#78:](https://github.com/pysal/inequality/pull/78) Wolfson bipolarization index
52 | * [#74:](https://github.com/pysal/inequality/pull/74) Atkinson inequality measures
53 | * [#77:](https://github.com/pysal/inequality/pull/77) reup lint+format - rename CI env
54 | * [#76:](https://github.com/pysal/inequality/issues/76) rename CI envs
55 | * [#73:](https://github.com/pysal/inequality/issues/73) drop `black` -> adopt `ruff` for formatting
56 | * [#72:](https://github.com/pysal/inequality/pull/72) Pengram
57 | * [#69:](https://github.com/pysal/inequality/pull/69) deprecation of _indices
58 | * [#67:](https://github.com/pysal/inequality/issues/67) CI adjustment needed for `numpy>=2.1.0.dev0`
59 |
60 |
61 |
62 | ### pointpats v2.5.1
63 | * [#148:](https://github.com/pysal/pointpats/pull/148) TST: relax assertion to avoid floating point issues
64 | * [#147:](https://github.com/pysal/pointpats/pull/147) COMPAT: compatibility with numpy
65 | * [#145:](https://github.com/pysal/pointpats/pull/145) ENH: support geopandas objects in distance statistics
66 |
67 |
68 |
69 | ### segregation v2.5.1
70 | * [#231:](https://github.com/pysal/segregation/pull/231) testing
71 | * [#230:](https://github.com/pysal/segregation/pull/230) old numpy nan
72 | * [#229:](https://github.com/pysal/segregation/pull/229) (bug) numpy 2.0 is not supporting np.NaN
73 |
74 |
75 |
76 |
77 | ### momepy v0.9.1
78 | * [#661:](https://github.com/pysal/momepy/pull/661) ENH: do not fail with 3d nodes - `preprocess.remove_false_nodes()`
79 | * [#660:](https://github.com/pysal/momepy/pull/660) DOC: clear the installation instructions
80 | * [#658:](https://github.com/pysal/momepy/pull/658) ENH: add Streetscape class
81 | * [#654:](https://github.com/pysal/momepy/pull/654) CI: pin fiona
82 | * [#675:](https://github.com/pysal/momepy/pull/675) Get mean of actual values (do not imply missing == 0) in Streetscape
83 | * [#673:](https://github.com/pysal/momepy/issues/673) Streetscape assumes 0 when nan is given
84 | * [#674:](https://github.com/pysal/momepy/pull/674) BUG: fix corner case of empty intersection in streetscape
85 | * [#671:](https://github.com/pysal/momepy/pull/671) BUG: fix extraction of ids if there is only a single hit when retrieving point level data in Streetscape
86 | * [#670:](https://github.com/pysal/momepy/pull/670) DOC: more osmnx compat
87 | * [#669:](https://github.com/pysal/momepy/pull/669) DOC: user guide compat with osmnx 2.0
88 | * [#668:](https://github.com/pysal/momepy/pull/668) Bump codecov/codecov-action from 4 to 5
89 | * [#667:](https://github.com/pysal/momepy/pull/667) ENH: retain index of buildings and plots intersecting sightlines
90 | * [#666:](https://github.com/pysal/momepy/pull/666) handling edge cases in `preprocessing.FaceArtifacts`
91 | * [#665:](https://github.com/pysal/momepy/issues/665) all equivalent `"face_artifact_index"` leads to `LinAlgError` in `preprocessing.FaceArtifacts`
92 | * [#664:](https://github.com/pysal/momepy/issues/664) fail gracefully when no initial polygons generated – `preprocessing.FaceArtifacts`
93 | * [#657:](https://github.com/pysal/momepy/issues/657) remove_false_nodes throws an unexpected error
94 | * [#659:](https://github.com/pysal/momepy/issues/659) momepy.COINS: raise ValueError if empty geodataframe is passed
95 | * [#656:](https://github.com/pysal/momepy/pull/656) [pre-commit.ci] pre-commit autoupdate
96 | * [#655:](https://github.com/pysal/momepy/pull/655) Bump mamba-org/setup-micromamba from 1 to 2
97 | * [#653:](https://github.com/pysal/momepy/pull/653) Allow nodes as an input to graph construction method 'gdf_to_nx'
98 | * [#652:](https://github.com/pysal/momepy/pull/652) let COINS run even if there's overlapping geometry
99 |
100 |
101 |
102 | ### spreg v1.8.1
103 | * [#170:](https://github.com/pysal/spreg/pull/170) Fixing GM_KPP in the presence of pandas DF
104 | * [#167:](https://github.com/pysal/spreg/pull/167) Updating DPG api listing
105 | * [#165:](https://github.com/pysal/spreg/pull/165) add spsearch to docs, rm sphinx-bibtex pin
106 | * [#166:](https://github.com/pysal/spreg/pull/166) Update tutorials.rst to include new notebooks
107 | * [#164:](https://github.com/pysal/spreg/pull/164) Update build docs using segregation's version
108 | * [#163:](https://github.com/pysal/spreg/pull/163) Spreg version 1.8
109 | * [#160:](https://github.com/pysal/spreg/pull/160) Adding spsearch.py
110 | * [#162:](https://github.com/pysal/spreg/pull/162) doc: Fix typo in DGP docs
111 | * [#159:](https://github.com/pysal/spreg/pull/159) Bump mamba-org/setup-micromamba from 1 to 2
112 | * [#158:](https://github.com/pysal/spreg/pull/158) Updating spreg to 1.7
113 | * [#156:](https://github.com/pysal/spreg/pull/156) `ruff` format repo
114 | * [#151:](https://github.com/pysal/spreg/issues/151) update `pre-commit` -- add `ruff` ; drop `black`
115 | * [#150:](https://github.com/pysal/spreg/issues/150) swap from `black` to `ruff` for formatting
116 | * [#154:](https://github.com/pysal/spreg/pull/154) update `environment.yml` & remove `.coveragerc`
117 | * [#149:](https://github.com/pysal/spreg/pull/149) build docs with 3.12 environment
118 | * [#153:](https://github.com/pysal/spreg/issues/153) purge `.coveragerc` - no longer needed
119 | * [#152:](https://github.com/pysal/spreg/issues/152) update `environment.yml`
120 | * [#148:](https://github.com/pysal/spreg/issues/148) Build docs failure
121 | * [#146:](https://github.com/pysal/spreg/issues/146) spatial diagnostics in OLS fail with Graph
122 | * [#147:](https://github.com/pysal/spreg/pull/147) Deprecating check_spat_diag
123 | * [#145:](https://github.com/pysal/spreg/pull/145) Updating to version 1.6.0
124 | * [#127:](https://github.com/pysal/spreg/issues/127) ENH: support pandas object as X, y
125 | * [#57:](https://github.com/pysal/spreg/issues/57) Docstring test failures
126 | * [#125:](https://github.com/pysal/spreg/issues/125) `scipy.sparse.csr` DeprecationWarning in `user_output.py`
127 | * [#135:](https://github.com/pysal/spreg/issues/135) 2 tests in CI failing [2024-04-23]
128 |
129 |
130 |
131 | ### tobler v0.12.1
132 | * [#223:](https://github.com/pysal/tobler/pull/223) h3compat
133 | * [#222:](https://github.com/pysal/tobler/pull/222) CI: fetch examples ahead of time
134 | * [#221:](https://github.com/pysal/tobler/pull/221) COMPAT: fix compatibility with scipy 1.15
135 | * [#219:](https://github.com/pysal/tobler/pull/219) DOCS: use nbsphinx-link and include myst beta
136 | * [#220:](https://github.com/pysal/tobler/pull/220) codecov4
137 | * [#204:](https://github.com/pysal/tobler/issues/204) Site docs return 404
138 | * [#213:](https://github.com/pysal/tobler/issues/213) docs action is failing
139 | * [#206:](https://github.com/pysal/tobler/pull/206) Docs notebook links
140 | * [#214:](https://github.com/pysal/tobler/pull/214) Docs
141 | * [#205:](https://github.com/pysal/tobler/pull/205) infrastructure
142 | * [#218:](https://github.com/pysal/tobler/pull/218) COMPAT: compatibility with h3 v4
143 | * [#207:](https://github.com/pysal/tobler/issues/207) update `h3fy` for h3 api changes
144 | * [#216:](https://github.com/pysal/tobler/issues/216) cut new release of tobler?
145 | * [#217:](https://github.com/pysal/tobler/issues/217) area_interpolate() fills gap areas where target and source geopandas frames don't intersect with 0s instead of NaNs
146 |
147 |
148 |
149 | ### mapclassify v2.8.1
150 | * [#229:](https://github.com/pysal/mapclassify/pull/229) fix nan handling in color array
151 | * [#230:](https://github.com/pysal/mapclassify/pull/230) pin max fiona in oldest
152 |
153 |
154 |
155 | ### splot v1.1.7
156 | * [#187:](https://github.com/pysal/splot/pull/187) BUG: fix scatter plots and regression lines in Moran plots
157 | * [#186:](https://github.com/pysal/splot/issues/186) BUG: OLS regression in moran is wrong
158 | * [#178:](https://github.com/pysal/splot/issues/178) BUG: plot_local_autocorrelation colors do not match between subplots
159 | * [#131:](https://github.com/pysal/splot/issues/131) Inconsistent colors for `moran_scatterplot` and `lisa_cluster` when p values are small
160 | * [#185:](https://github.com/pysal/splot/pull/185) Consistent hot/cold spot colors for local moran
161 | * [#184:](https://github.com/pysal/splot/pull/184) (bug) support graph in moran's viz
162 |
163 |
164 |
165 | ### pysal v25.01
166 | * [#1337:](https://github.com/pysal/pysal/pull/1337) removing `spvcm` from meta release -- archived - #1330
167 | * [#1330:](https://github.com/pysal/pysal/issues/1330) inclusion of `spvcm` in PySAL meta-release
168 | * [#1362:](https://github.com/pysal/pysal/pull/1362) 25.01rc1
169 | * [#1360:](https://github.com/pysal/pysal/pull/1360) Bump codecov/codecov-action from 4 to 5
170 | * [#1358:](https://github.com/pysal/pysal/pull/1358) Bump mamba-org/setup-micromamba from 1 to 2
171 | * [#1357:](https://github.com/pysal/pysal/pull/1357) pin fiona in oldest
172 | * [#1355:](https://github.com/pysal/pysal/pull/1355) MNT: New release and publish action
173 |
174 |
175 | ## Contributors
176 |
177 | Many thanks to all of the following individuals who contributed to this release:
178 |
179 |
180 | - Eli Knaap
181 | - James Gaboardi
182 | - Josiah Parry
183 | - Knaaptime
184 | - Krasen Samardzhiev
185 | - Martin Fleischmann
186 | - Pedro Amaral
187 | - Serge Rey
188 | - Wei Kang
189 |
190 |
191 |
--------------------------------------------------------------------------------
/tools/frozen.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import json
4 | from release_info import get_github_info
5 |
6 | releases = get_github_info()
7 |
8 | requirements = []
9 | frozen_dict = {}
10 | for package in releases:
11 | version = releases[package]['version']
12 | version = version.replace("v","")
13 | version = version.replace("V","")
14 | requirements.append(f'{package}>={version}')
15 | frozen_dict[package] = version
16 |
17 | with open('frozen.txt', 'w') as f:
18 | f.write("\n".join(requirements))
19 |
20 | import pickle
21 | pickle.dump(releases, open( "releases.p", "wb" ) )
22 |
23 |
--------------------------------------------------------------------------------
/tools/gitcount.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | # # PySAL Change Log Statistics
5 |
6 | # urllib3>=1.26
7 | # python-dateutil<=2.8.0
8 | # pytest
9 | # pytest-cov
10 | # coverage## Approach
11 | # - get date of last gh release of each package -> github_released
12 | # - get date of last pypi release of each package -> pypi_released
13 | # - get data of last meta-release -> start_date
14 | # - for each package
15 | # - get issues between start_date and package_released in master/main
16 | # - get pulls between start_date and package_released in master/main
17 |
18 |
19 | import pickle
20 | from release_info import (issues_closed_since, packages,
21 | is_pull_request,
22 | sorted_by_field,
23 | clone_defaults,
24 | release_date,
25 | start_date,
26 | PYSALVER,
27 | USER
28 | )
29 | import datetime
30 | packages.append('pysal')
31 | clone_defaults(packages)
32 | since = datetime.datetime.combine(start_date, datetime.time(0, 0))
33 | issues = {}
34 | for package in packages:
35 | issues[package] = issues_closed_since(since, project=f'pysal/{package}')
36 | pulls = {}
37 | for package in packages:
38 | pulls[package] = issues_closed_since(since, project=f'pysal/{package}',
39 | pulls=True)
40 | pickle.dump(issues, open("issues_closed.p", "wb"))
41 |
42 | pickle.dump(pulls, open("pulls_closed.p", "wb"))
43 |
--------------------------------------------------------------------------------
/tools/release.yaml:
--------------------------------------------------------------------------------
1 | version: 25.01
2 | release_date: 2025-01-31
3 | start_date: 2024-07-31
4 | user: sjsrey
5 |
--------------------------------------------------------------------------------
/tools/release_info.py:
--------------------------------------------------------------------------------
1 | """
2 | Grab most recent releases tagged on Github for PySAL subpackages
3 |
4 |
5 | TODO
6 | - [ ] update dependencies in pyproj.toml for pinning new releases of
7 | pysal packages
8 |
9 | """
10 |
11 | import os
12 | import subprocess
13 | import json
14 | import urllib
15 | import re
16 | import yaml
17 | from urllib.request import urlopen
18 | from datetime import datetime, timedelta
19 | import requests
20 |
21 |
22 | with open("release.yaml", "r") as stream:
23 | info = yaml.safe_load(stream)
24 |
25 | release_date = info["release_date"]
26 | PYSALVER = info["version"]
27 | start_date = info["start_date"]
28 | USER = info["user"]
29 |
30 |
31 | ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
32 | PER_PAGE = 100
33 | element_pat = re.compile(r"<(.+?)>")
34 | rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
35 |
36 |
37 | # get github token:
38 | with open("token", "r") as token_file:
39 | token = token_file.read().strip()
40 |
41 | gh_session = requests.Session()
42 | gh_session.auth = (USER, token)
43 |
44 |
45 | packages = [
46 | "libpysal",
47 | "access",
48 | "esda",
49 | "giddy",
50 | "inequality",
51 | "pointpats",
52 | "segregation",
53 | "spaghetti",
54 | "mgwr",
55 | "momepy",
56 | "spglm",
57 | "spint",
58 | "spreg",
59 | "tobler",
60 | "mapclassify",
61 | "splot",
62 | "spopt",
63 | ]
64 |
65 |
66 | def get_github_info(packages=packages):
67 | """
68 | Get information about subpackage releases that have been tagged on github
69 | """
70 | no_release = []
71 | release = {}
72 |
73 | for package in packages:
74 | url = f"https://api.github.com/repos/pysal/{package}/releases/latest"
75 | print(url)
76 | d = json.loads(gh_session.get(url).text)
77 | if "message" in d:
78 | if d["message"] == "Not Found":
79 | print(f"{package} has no latest release")
80 | no_release.append(package)
81 | else:
82 | print("Something else happened")
83 | print(d)
84 | else:
85 | tag_name = d["tag_name"]
86 | tarball_url = d["tarball_url"]
87 | release[package] = {
88 | "version": tag_name,
89 | "url": tarball_url,
90 | "release_date": d["published_at"],
91 | }
92 |
93 | with open("tarballs.json", "w") as fp:
94 | json.dump(release, fp)
95 |
96 | for package in release:
97 | dt = release[package]["release_date"]
98 | date_format = "%Y-%m-%dT%H:%M:%SZ"
99 | release_date = datetime.strptime(dt, date_format)
100 | release[package]["release_date"] = release_date
101 |
102 | return release
103 |
104 |
105 | def get_pypi_info():
106 | """
107 | Get information about subpackage releases that have been tagged on pypi
108 | """
109 | releases = {}
110 | for package in packages:
111 | url = f"https://pypi.python.org/pypi/{package}/json"
112 | data = json.load(urllib.request.urlopen(url))
113 | keys = list(data["releases"].keys())
114 | last = keys[-1]
115 | release = data["releases"][last][0]["upload_time"]
116 | release_date = datetime.strptime(release, "%Y-%m-%dT%H:%M:%S")
117 | releases[package] = {"version": last, "released": release_date}
118 |
119 | return releases
120 |
121 |
122 | def clone_masters():
123 | clone_releases(tag="master")
124 |
125 |
126 | def clone_mains():
127 | clone_releases(tag="main")
128 |
129 |
130 | def clone_defaults(packages=packages, cwd=os.getcwd()):
131 | for package in packages:
132 | directory_path = f"tmp/{package}"
133 |
134 | # if already cloned, we pull, otherwise clone
135 | if os.path.isdir(directory_path):
136 | print(f"{directory_path} exists, git pull required")
137 | os.chdir(directory_path)
138 | result = subprocess.run(["git", "pull"],
139 | capture_output=True, text=True)
140 | print("Output:\n", result.stdout)
141 | print("Errors:\n", result.stderr)
142 | os.chdir(cwd)
143 | else:
144 | url = f"https://api.github.com/repos/pysal/{package}"
145 | data = json.load(urllib.request.urlopen(url))
146 | branch = data["default_branch"]
147 | pkgstr = (
148 | f"git clone --branch {branch}"
149 | f" https://github.com/pysal/{package}.git"
150 | f" tmp/{package}"
151 | )
152 | print(pkgstr)
153 | os.system(pkgstr)
154 |
155 |
156 | def clone_releases(tag=None):
157 | """
158 | Clone the releases in tmprelease_date
159 | """
160 | os.system("rm -rf tmp")
161 | os.system("mkdir tmp")
162 | for package in packages:
163 | print(package, packages[package])
164 | if tag:
165 | branch = tag
166 | else:
167 | branch = packages[package]
168 | pkgstr = (
169 | f"git clone --branch {branch}"
170 | f" https://github.com/pysal/{package}.git"
171 | f" tmp/{package}"
172 | )
173 | print(pkgstr)
174 | os.system(pkgstr)
175 |
176 |
177 | def parse_link_header(headers):
178 | link_s = headers.get("link", "")
179 | urls = element_pat.findall(link_s)
180 | rels = rel_pat.findall(link_s)
181 | d = {}
182 | for rel, url in zip(rels, urls):
183 | d[rel] = url
184 | return d
185 |
186 |
187 | def get_paged_request(url):
188 | """get a full list, handling APIv3's paging"""
189 | results = []
190 | while url:
191 | # print("fetching %s" % url, file=sys.stderr)
192 | f = urlopen(url)
193 | results.extend(json.load(f))
194 | links = parse_link_header(f.headers)
195 | url = links.get("next")
196 | return results
197 |
198 |
199 | def _parse_datetime(s):
200 | """Parse dates in the format returned by the Github API."""
201 | if s:
202 | return datetime.strptime(s, ISO8601)
203 | else:
204 | return datetime.fromtimestamp(0)
205 |
206 |
207 | def issues2dict(issues):
208 | """Convert a list of issues to a dict, keyed by issue number."""
209 | idict = {}
210 | for i in issues:
211 | idict[i["number"]] = i
212 | return idict
213 |
214 |
215 | def get_url(url):
216 | d = json.loads(gh_session.get(url).text)
217 | return d
218 |
219 |
220 | def get_issues(project="pysal/pysal", state="closed", pulls=False):
221 | """Get a list of the issues from Github api"""
222 | which = "pulls" if pulls else "issues"
223 | url = f"https://api.github.com/repos/{project}/{which}?state={state}"
224 | return get_url(url)
225 |
226 |
227 | def is_pull_request(issue):
228 | """Return True if the given issue is a pull request."""
229 | return "pull_request_url" in issue
230 |
231 |
232 | def issues_closed_since(
233 | period=timedelta(days=365), project="pysal/pysal", pulls=False
234 | ):
235 | """Get all issues closed since a particular point in time. period
236 | can either be a datetime object, or a timedelta object. In the
237 | latter case, it is used as a time before the present."""
238 |
239 | which = "pulls" if pulls else "issues"
240 |
241 | if isinstance(period, timedelta):
242 | period = datetime.now() - period
243 |
244 | url = (
245 | "https://api.github.com/repos/{}/{}?state=closed&sort=updated&since={}"
246 | "&per_page={}".format(
247 | project, which, period.strftime(ISO8601), PER_PAGE
248 | )
249 | )
250 |
251 | allclosed = get_url(url)
252 | filtered = [
253 | i for i in allclosed if _parse_datetime(i["closed_at"]) > period
254 | ]
255 |
256 | # exclude rejected PRs
257 | if pulls:
258 | filtered = [pr for pr in filtered if pr["merged_at"]]
259 |
260 | return filtered
261 |
262 |
263 | def sorted_by_field(issues, field="closed_at", reverse=False):
264 | """Return a list of issues sorted by closing date date."""
265 | return sorted(issues, key=lambda i: i[field], reverse=reverse)
266 |
267 |
268 | def report(issues, show_urls=False):
269 | """Summary report about a list of issues, printing number and title."""
270 | # titles may have unicode in them, so we must encode everything below
271 | if show_urls:
272 | for i in issues:
273 | role = "ghpull" if "merged_at" in i else "ghissue"
274 | title = i["title"].encode("utf-8")
275 | print(f"* :{role}:`{i['number']}`: {title}")
276 | else:
277 | for i in issues:
278 | title = i["title"].encode("utf-8")
279 | print(f"* {i['number']}: {title}")
280 |
281 |
282 | def get_meta_releases():
283 | url = "https://api.github.com/repos/pysal/pysal/releases"
284 | return get_url(url)
285 |
--------------------------------------------------------------------------------