Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix flaky fetch tests #255

Merged
merged 4 commits into from
Dec 15, 2022
Merged

fix flaky fetch tests #255

merged 4 commits into from
Dec 15, 2022

Conversation

nclack
Copy link
Collaborator

@nclack nclack commented Nov 15, 2022

Two test issues here:

It seems like hitting the pypi and hub api's failed to return napari-svg sometimes. For pypi this is about 1 out of every 2 or 3 attempts. For hub it's rarer. Not quite sure why it's happening. There's meaningful data in the response either way, but sometimes napari-svg isn't in the return. Not sure what the best fix is here. For now, I just check that the response data is non-empty.

The fetch-cli test was failing because it's looking at napari-omero which has a unicode red exclamation mark in it's package metadata. This causes character validation errors where the encoding of the string is unspecified. This is fixed by specifying 'utf-8' in a couple places in the pr.

@codecov
Copy link

codecov bot commented Nov 15, 2022

Codecov Report

Merging #255 (e518cb2) into main (078b601) will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##              main      #255   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           37        37           
  Lines         2806      2806           
=========================================
  Hits          2806      2806           
Impacted Files Coverage Δ
npe2/cli.py 100.00% <100.00%> (ø)
npe2/manifest/_bases.py 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@nclack
Copy link
Collaborator Author

nclack commented Nov 15, 2022

Not sure why napari tests is failing.

Maybe disable_notification_dismiss_timer in conftest is causing problems 😕

Copy link
Member

@kne42 kne42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@andy-sweet andy-sweet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem with the code changes, but you'll need to fix the napari tests before merging. As discussed offline, I think one way to do this is make a PR to napari/napari that moves disable_notification_dismiss_timer to test_qt_notifications.py, so that it's only auto-used in that test module (which shouldn't be run here).

@aganders3
Copy link
Contributor

I don't really know why I ended up looking into this, but it looks like the issue is scraping PyPI defaulting to "Most Relevant" sorting which seems to be unstable. This might ultimately be a warehouse bug because that kinda sucks even if you're using a browser. Ordering by "Date last updated" seems to fix it (at least I was able to run 331 times in a row without the failure):

diff --git a/npe2/_inspection/_fetch.py b/npe2/_inspection/_fetch.py
index 23e2d76..bf4a5b0 100644
--- a/npe2/_inspection/_fetch.py
+++ b/npe2/_inspection/_fetch.py
@@ -384,7 +384,7 @@ def _get_packages_by_classifier(classifier: str) -> Dict[str, str]:
 
     packages = {}
     page = 1
-    url = f"https://pypi.org/search/?c={parse.quote_plus(classifier)}&page="
+    url = f"https://pypi.org/search/?q=&o=-created&c={parse.quote_plus(classifier)}&page="
     while True:
         try:
             with request.urlopen(f"{url}{page}") as response:

With the above change (note 264 is the number of plugins the web interface reports as well):

>>> for i in range(10):
...     print(len(get_pypi_plugins()))
... 
264
264
264
264
264
264
264
264
264
264

Without this change:

>>> for i in range(10):
...     print(len(get_pypi_plugins()))
... 

251
251
251
263
250
250
251
251
251
251

@aganders3
Copy link
Contributor

Another option would be to use the (to-be-deprecated) XML RPC API:
https://warehouse.pypa.io/api-reference/xml-rpc.html#browse-classifiers

@aganders3
Copy link
Contributor

aganders3 commented Nov 30, 2022

Another option would be to use the (to-be-deprecated) XML RPC API:
https://warehouse.pypa.io/api-reference/xml-rpc.html#browse-classifiers

This might be the way - it's also super fast (~300 ms vs ~6s). The results seem to already be sorted but I'm not sure this is guaranteed and it's natural case-insensitive sorted so a bit different from the current implementation but that would be an easy fix. I'm pretty sure the version sorting is okay so this works but we might want to iterate the results to make sure.

diff --git a/npe2/_inspection/_fetch.py b/npe2/_inspection/_fetch.py
index 23e2d76..d1a1b69 100644
--- a/npe2/_inspection/_fetch.py
+++ b/npe2/_inspection/_fetch.py
@@ -6,6 +6,7 @@ import os
 import re
 import subprocess
 import tempfile
+import xmlrpc.client
 from concurrent.futures import ProcessPoolExecutor
 from contextlib import contextmanager
 from functools import lru_cache
@@ -376,27 +377,11 @@ def _get_packages_by_classifier(classifier: str) -> Dict[str, str]:
 
     Returns
     -------
-    packages : List[str]
+    packages : Dict[str, str]
         name of all packages at pypi that declare ``classifier``
     """
-    PACKAGE_NAME_PATTERN = re.compile('class="package-snippet__name">(.+)</span>')
-    PACKAGE_VERSION_PATTERN = re.compile('class="package-snippet__version">(.+)</span>')
-
-    packages = {}
-    page = 1
-    url = f"https://pypi.org/search/?c={parse.quote_plus(classifier)}&page="
-    while True:
-        try:
-            with request.urlopen(f"{url}{page}") as response:
-                html = response.read().decode()
-            names = PACKAGE_NAME_PATTERN.findall(html)
-            versions = PACKAGE_VERSION_PATTERN.findall(html)
-            packages.update(dict(zip(names, versions)))
-            page += 1
-        except error.HTTPError:
-            break
-
-    return dict(sorted(packages.items()))
+    client = xmlrpc.client.ServerProxy('https://pypi.org/pypi')
+    return dict(client.browse([classifier]))
Sample output
{'affinder': '0.2.3',
 'arcos-gui': '0.0.6',
 'avidaq': '0.0.5',
 'bbii-decon': '0.0.1',
 'beetlesafari': '0.4.0',
 'bfio': '2.3.0.dev0',
 'blik': '0.3.3',
 'brainglobe-napari-io': '0.1.5rc0',
 'brainreg-napari': '0.0.5rc0',
 'brainreg-segment': '0.2.8rc0',
 'btrack': '0.4.6',
 'cellfinder-napari': '0.0.9rc0',
 'cellpose-napari': '0.1.5',
 'devbio-napari': '0.8.0',
 'disease-classifier': '0.0.1',
 'elastix-napari': '0.1.7',
 'empanada-napari': '0.2.3',
 'example-plugin': '0.0.7',
 'faser': '0.2.9',
 'flood-napari': '0.0.1',
 'grabber-ift': '0.2.2',
 'guanine-crystal-analysis': '0.0.1',
 'hesperos': '0.1.9',
 'iacs-ipac-reader': '0.0.7',
 'Image-Composer': '0.0.19',
 'Image-Part-Selecter': '0.0.7',
 'imaxt-multiscale-plugin': '0.2.0',
 'koopa-viz': '0.0.1',
 'Label-Creator': '0.0.9',
 'Layer-Data-Replace': '0.0.5',
 'mikro-napari': '0.1.49',
 'misic-napari': '0.2.2',
 'morphometrics': '0.0.6',
 'morphometrics-engine': '0.0.1',
 'napari-3d-ortho-viewer': '0.0.1',
 'napari-accelerated-pixel-and-object-classification': '0.9.0',
 'napari-affinities': '0.1.3',
 'napari-aicsimageio': '0.7.2',
 'napari-aideveloper': '0.0.4',
 'napari-allencell-annotator': '1.0.7',
 'napari-allencell-segmenter': '2.1.3',
 'napari-animated-gif-io': '0.1.2',
 'napari-animation': '0.0.5',
 'napari-annotate': '0.0.1',
 'napari-annotator': '0.0.1',
 'napari-annotatorj': '0.0.7',
 'napari-apple': '0.0.1',
 'napari-apr-viewer': '1.0.0',
 'napari-arboretum': '0.1.1',
 'napari-assistant': '0.4.3',
 'napari-assistant-plugin-generator': '0.1.0',
 'napari-bigwarp': '0.0.1',
 'napari-bil-data-viewer': '0.1.2',
 'napari-bioformats': '0.2.1',
 'napari-bioimageio': '0.1.3',
 'napari-bio-sample-data': '0.0.4',
 'napari-bleach-correct': '0.0.1',
 'napari-blob-detection': '0.0.2',
 'napari-blossom': '0.0.2',
 'napari-boids': '0.0.1',
 'napari-boxmanager': '0.3.0b3',
 'napari-brightness-contrast': '0.1.8',
 'napari-brushsettings': '0.0.2',
 'napari-btrack-reader': '0.1.0',
 'napari-bud-cell-segmenter': '0.1.2',
 'napari-buds': '0.1.2',
 'napari-calibration': '0.0.9',
 'napari-ccp4map': '1.0',
 'napari-cellseg3d': '0.0.1rc4',
 'napari-checkerboard': '0.0.3',
 'napari-cilia-beating-frequency': '0.1',
 'napari-clemreg': '0.0.1a4',
 'napari-clusters-plotter': '0.5.2',
 'napari-compressed-labels-io': '0.0.2',
 'napari-console': '0.0.6',
 'napari-cookiecut': '0.1.1',
 'napari-crop': '0.1.6',
 'napari-cryofibsem-monitor': '0.0.3',
 'napari-cupy-image-processing': '0.3.1',
 'napari-curtain': '0.1.1',
 'napari-czann-segment': '0.0.16',
 'napari-czifile': '0.0.2',
 'napari-czifile2': '0.2.7',
 'napari-deepfinder': '0.0.1',
 'napari-deeplabcut': '0.0.8',
 'napari-deepmeta': '2.1',
 'napari-DeepSpot': '0.0.7',
 'napari-demo': '0.2.3',
 'napari-denoiseg': '0.0.1rc2',
 'napari-dexp': '0.0.7',
 'napari-dv': '0.3.0',
 'napari-dvid': '0.2.0',
 'napari-dzi-zarr': '0.1.2',
 'napari-elementary-numpy-operations': '0.0.5',
 'napari-em-reader': '0.1.0',
 'napari-error-reporter': '0.3.1',
 'napari-feature-classifier': '0.0.1',
 'napari-features': '0.1.4',
 'napari-filament-annotator': '0.1.2',
 'napari-filaments': '0.2.1',
 'napari-folder-browser': '0.1.3',
 'napari-generic-SIMulator': '0.0.9',
 'napari-geojson': '0.1.3',
 'napari-hdf5-labels-io': '0.3.dev16',
 'napari-help': '0.1.0',
 'napari-IDS': '0.0.8',
 'napari-imagecodecs': '0.0.2',
 'napari-image-stacker': '0.1.9',
 'napari-imaris-loader': '0.1.8',
 'napari-imc': '0.6.5',
 'napari-imsmicrolink': '0.1.8',
 'napari-input-visualizer': '0.0.1',
 'napari-IP-workflow': '0.0.3',
 'napari-ISM': '0.1.1',
 'napari-itk-io': '0.2.0',
 'napari-J': '0.2.4',
 'napari-kics': '0.0.3rc6',
 'napari-labelimg4classification': '0.1.1',
 'napari-labeling': '0.1.2',
 'napari-label-interpolator': '0.1.0',
 'napari-labelling-assistant': '0.0.5',
 'napari-labels-overlap': '0.0.3',
 'napari-lattice': '0.2.4',
 'napari-layer-details-display': '0.1.4',
 'napari-layer-table': '0.0.9',
 'napari-lazy-openslide': '0.3.0',
 'napari-LF': '0.0.4',
 'napari-lfdfiles': '0.0.2',
 'napari-live-flim': '0.1.0',
 'napari-live-recording': '0.2.1',
 'napari-mahotas-image-processing': '0.1.2',
 'napari-manual-split-and-merge-labels': '0.1.3',
 'napari-manual-transforms': '0.0.3',
 'napari-mat-file-reader': '0.0.2',
 'napari-math': '0.0.1b0',
 'napari-mat-images': '0.1.3',
 'napari-matplotlib': '0.2.1',
 'napari-medical-image-formats': '0.3.8',
 'napari-merge-stardist-masks': '0.0.3',
 'napari-metroid': '0.0.5',
 'napari-micromanager': '0.0.1rc7',
 'napari-microscope': '0.0.3',
 'napari-mm3': '0.0.8',
 'napari-molecule-reader': '0.1.2',
 'napari-mouse-controls': '0.1.3',
 'napari-mrcfile-handler': '0.0.6',
 'napari-mrcfile-reader': '0.1.3',
 'napari-mri': '0.1.0',
 'napari-multitask': '0.0.2',
 'napari-n2v': '0.0.4',
 'napari-nasa-samples': '0.0.5',
 'napari-nd2-folder-viewer': '0.0.4',
 'napari-nD-annotator': '0.0.8',
 'napari-nd-cropper': '0.1.3',
 'napari-ndtiffs': '0.1.2rc0',
 'napari-netpbmfile': '0.0.2',
 'napari-nikon-nd2': '0.1.3',
 'napari-nlm': '0.0.4',
 'napari-nucleaizer': '0.2.4',
 'napari-oclrfc': '0.4.5',
 'napari-oiffile': '0.0.2',
 'napari-omaas': '0.1.0',
 'napari-omero': '0.2.0',
 'napari-ome-zarr': '0.5.2',
 'napari-organoid-counter': '0.1.1',
 'napari-owncloud': '0.1.2',
 'napari-patchcreator': '0.1.4',
 'napari-pdf-reader': '0.0.1a3',
 'napari-pdr-reader': '0.0.1',
 'napari-PHILOW': '0.0.1',
 'napari-PICASSO': '0.3.0',
 'napari-pixel-correction': '0.1.4',
 'napari-plot': '0.1.5',
 'napari-plot-profile': '0.2.2',
 'napari-plugin-search': '0.1.3',
 'napari-power-spectrum': '0.0.6',
 'napari-power-widgets': '0.0.1',
 'napari-pram': '0.1.3',
 'napari-process-points-and-surfaces': '0.3.3',
 'napari-proofread-brainbow': '0.3.0',
 'napari-properties-plotter': '0.2.2',
 'napari-properties-viewer': '0.0.2',
 'napari-psf-analysis': '0.2.5',
 'napari-psf-simulator': '0.1.5',
 'napari-pssr': '0.1',
 'napari-pyclesperanto-assistant': '0.9.9',
 'napari-pymeshlab': '0.0.5',
 'napari-pystackreg': '0.1.3',
 'napari-quoll': '0.0.1',
 'napari-rioxarray': '0.0.1',
 'napari-roi': '0.1.7',
 'napari-roi-registration': '0.1.2',
 'napari-sairyscan': '0.0.2',
 'napari-sc3D-viewer': '0.2.0',
 'napari-script-editor': '0.2.9',
 'napari-sdeconv': '1.0.1',
 'napari-sdtfile': '0.0.2',
 'napari-segment': '0.3.6',
 'napari-segment-blobs-and-things-with-membranes': '0.3.3',
 'napari-serialcellpose': '0.2.2',
 'napari-sif-reader': '0.0.1',
 'napari-sift-registration': '0.1.2',
 'napari-simpleitk-image-processing': '0.4.4',
 'napari-sim-processor': '0.0.9',
 'napari-sim-SIMulator': '0.0.1',
 'napari-skimage-regionprops': '0.5.6',
 'napari-spacetx-explorer': '0.1.8',
 'napari-spatialdata': '0.1.0',
 'napari-spatial-omics': '0.0.8',
 'napari-splinedist': '0.3.1',
 'napari-splineit': '0.3.0',
 'napari-spreadsheet': '0.0.1',
 'napari-stable-diffusion': '0.0.1',
 'napari-steinpose': '0.1.0',
 'napari-stl-exporter': '0.0.9',
 'napari-stracking': '0.1.9',
 'napari-stress': '0.1.2',
 'napari-subboxer': '0.0.1',
 'napari-svetlana': '0.1.4',
 'napari-svg': '0.1.6',
 'napari-tabu': '0.1.5',
 'napari-text-layer': '0.1.2',
 'napari-threedee': '0.0.1',
 'napari-tifffile': '0.0.2',
 'napari-tiler': '0.0.9',
 'napari-timeseries-opener-plugin': '0.1.8',
 'napari-time-series-plotter': '0.0.5',
 'napari-time-slicer': '0.4.9',
 'napari-tissuumaps': '1.1.2',
 'napari-tomoslice': '0.0.7',
 'napari-tracks-reader': '0.1.3',
 'napari-trait2d': '0.1.4',
 'napari-turing': '0.3.2',
 'napari-tyssue': '0.1.1',
 'napari-umap': '0.0.1',
 'napari-unicell': '0.0.1.post3',
 'napari-validate-random-label-predictions': '0.0.1',
 'napari-vesicles-segmentation': '0.0.1',
 'napari_video': '0.2.9',
 'napari-video-cvdask': '0.2.1',
 'napari-webcam': '0.1.18',
 'napari-workflow-inspector': '0.2.2',
 'napari-workflow-optimizer': '0.1.4',
 'napari-wsireg': '0.1.2',
 'napari-yapic-prediction': '0.2.0',
 'napari-yolov5': '0.2.9',
 'napari-zelda': '0.1.9',
 'napping': '0.2.3',
 'natari': '0.2.7',
 'nd2-dask': '0.0.7.dev1',
 'nfinder': '0.3',
 'nucleaizer-backend': '0.2.0',
 'Offset-Subtraction': '0.0.5',
 'okapi-em': '0.0.6',
 'ome-zarr': '0.0.19',
 'ortho-view-napari': '0.1.1',
 'palmari': '0.2.8',
 'Partial-Aligner': '0.0.1',
 'PartSeg': '0.14.6',
 'PartSeg-smfish': '0.1.2',
 'platelet-unet-watershed': '0.0.3',
 'PlatyMatch': '0.0.3',
 'psfmodels': '0.3.2',
 'pyclesperanto-prototype': '0.10.9',
 'pycudadecon': '0.4.0',
 'recOrder-napari': '0.3.0rc0',
 'RedLionfish': '0.8',
 'skeleton-finder': '0.0.2',
 'smo': '2.0.0',
 'snouty-viewer': '0.0.4',
 'stardist-napari': '2022.7.5',
 'the-segmentation-game': '0.2.0',
 'tracking-challenge-demo': '0.0.1',
 'vessel-express-napari': '0.0.8',
 'vollseg-napari': '2.3.7',
 'waver': '0.0.4',
 'workshop-demo': '0.0.2',
 'World2Data': '0.0.3',
 'yt-napari': '0.0.1',
 'zarpaint': '0.2.0'}

@nclack
Copy link
Collaborator Author

nclack commented Dec 13, 2022

napari/napari#5388 fixed the last remaining issue I was having

@nclack
Copy link
Collaborator Author

nclack commented Dec 13, 2022

@aganders3 Thanks for looking into the xmlrpc api!

Given that it's on the way to deprecation, do you still think that's the right approach? Is there a simple api equivalent?

@aganders3
Copy link
Contributor

I'm not sure - there's a bit of history here including past requests from napari: pypi/warehouse#9156. Their timeline is very vague but I would suspect they will not remove it until there is an equivalent new API. If not I would hope for a concrete deprecation period to be announced at some point.

One idea is to use the XML RPC API and fall back on the current method. The XML RPC API has some methods already deprecated that now raise a RuntimeError so we can probably anticipate the same path. I'm likewise not too confident in the stability of the interface we're currently scraping, but there doesn't seem to be a clear path forward for this capability.

Regardless I think it could be saved for another PR - for now I guess I would just recommend adding the change proposed in my other comment above #255 (comment). It's small and could be added here or I can make another PR for that.

@tlambert03 tlambert03 mentioned this pull request Dec 14, 2022
@nclack nclack merged commit 0db5c3e into main Dec 15, 2022
@nclack nclack deleted the fix-flaky-fetch-tests branch December 15, 2022 00:22
@kne42 kne42 added the tests related to testing or CI label Jan 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tests related to testing or CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants