diff --git a/.gitignore b/.gitignore index fc41eb8..e013bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,10 +40,12 @@ htmlcov/ .coverage .coverage.* .cache +coverage/ nosetests.xml coverage.xml *,cover .hypothesis/ +*.lock # Translations *.mo diff --git a/docs/Makefile b/docs/Makefile index d4bb2cb..3fb41af 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build +SPHINXBUILD ?= uv run sphinx-build SOURCEDIR = . BUILDDIR = _build diff --git a/examples/basic-demo-tableload.ipynb b/examples/basic-demo-tableload.ipynb index 0cb692c..eb4f266 100644 --- a/examples/basic-demo-tableload.ipynb +++ b/examples/basic-demo-tableload.ipynb @@ -52,7 +52,7 @@ "metadata": {}, "outputs": [], "source": [ - "url='https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", "# url=http://127.0.0.1:8080/firefly # if you have firefly server running locally\n", "\n", "fc = FireflyClient.make_client(url)" @@ -89,7 +89,10 @@ "source": [ "import os\n", "\n", - "testdata_repo_path = '/hydra/cm' # to be reset to where test files are located on your system" + "\n", + "testdata_repo_path = (\n", + " \"/hydra/cm\" # to be reset to where test files are located on your system\n", + ")" ] }, { @@ -112,7 +115,7 @@ "metadata": {}, "outputs": [], "source": [ - "localfile = os.path.join(testdata_repo_path, 'MF.20210502.18830.fits')\n", + "localfile = os.path.join(testdata_repo_path, \"MF.20210502.18830.fits\")\n", "filename = fc.upload_file(localfile)\n", "\n", "fc.show_table(filename)" @@ -154,10 +157,10 @@ "metadata": {}, "outputs": [], "source": [ - "localfile = os.path.join(testdata_repo_path, 'Mr31objsearch.xml')\n", + "localfile = os.path.join(testdata_repo_path, \"Mr31objsearch.xml\")\n", "filename = fc.upload_file(localfile)\n", "\n", - "fc.show_table(filename, tbl_id='votable-0')" + "fc.show_table(filename, tbl_id=\"votable-0\")" ] }, { @@ -173,7 +176,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table(filename, tbl_id='votable-1', table_index=1)" + "fc.show_table(filename, tbl_id=\"votable-1\", table_index=1)" ] }, { diff --git a/examples/demo-3color.ipynb b/examples/demo-3color.ipynb index 70067a1..12d7f9a 100644 --- a/examples/demo-3color.ipynb +++ b/examples/demo-3color.ipynb @@ -7,10 +7,12 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "#url='https://irsa.ipac.caltech.edu/irsaviewer'\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "# url='https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -30,44 +32,45 @@ "source": [ "# Add a three color fits\n", "# in cell 0, 0, 1, 1\n", - "target = '210.80227;54.34895;EQ_J2000' #Galaxy M101\n", - "viewer_id = '3C'\n", - "r = fc.add_cell(0, 0, 1, 1, 'images', viewer_id)\n", - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "if r['success']:\n", - " threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " }]\n", - " \n", - " fc.show_fits_3color(threeC, plot_id='wise_m101', viewer_id=viewer_id)" + "target = \"210.80227;54.34895;EQ_J2000\" # Galaxy M101\n", + "viewer_id = \"3C\"\n", + "r = fc.add_cell(0, 0, 1, 1, \"images\", viewer_id)\n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "if r[\"success\"]:\n", + " threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " ]\n", + "\n", + " fc.show_fits_3color(threeC, plot_id=\"wise_m101\", viewer_id=viewer_id)" ] }, { @@ -91,7 +94,7 @@ ], "source": [ "# Set stretch using hue-preserving algorithm with scaling coefficients .15 for RED, 1.0 for GREEN, and .4 for BLUE.\n", - "fc.set_stretch_hprgb(plot_id='wise_m101', asinh_q_value=4.2, scaling_k=[.15,1,.4])" + "fc.set_stretch_hprgb(plot_id=\"wise_m101\", asinh_q_value=4.2, scaling_k=[0.15, 1, 0.4])" ] }, { @@ -113,7 +116,7 @@ ], "source": [ "# Set per-band stretch\n", - "fc.set_stretch(plot_id='wise_m101', stype='ztype', algorithm='asinh', band='ALL')" + "fc.set_stretch(plot_id=\"wise_m101\", stype=\"ztype\", algorithm=\"asinh\", band=\"ALL\")" ] }, { @@ -134,7 +137,7 @@ ], "source": [ "# Set contrast and bias for each band\n", - "fc.set_rgb_colors(plot_id='wise_m101', bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2]) " + "fc.set_rgb_colors(plot_id=\"wise_m101\", bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2])" ] }, { @@ -155,7 +158,7 @@ ], "source": [ "# Set to use red and blue band only, with default bias and contrast\n", - "fc.set_rgb_colors(plot_id='wise_m101', use_green=False) " + "fc.set_rgb_colors(plot_id=\"wise_m101\", use_green=False)" ] }, { diff --git a/examples/demo-HiPS.ipynb b/examples/demo-HiPS.ipynb index e478c27..e751bbd 100644 --- a/examples/demo-HiPS.ipynb +++ b/examples/demo-HiPS.ipynb @@ -30,9 +30,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -48,7 +50,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n" + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" ] }, { @@ -57,20 +59,20 @@ "metadata": {}, "outputs": [], "source": [ - "target='148.892;69.0654;EQ_J2000'\n", - "fov_deg=0.5\n", - "size=0.2\n", + "target = \"148.892;69.0654;EQ_J2000\"\n", + "fov_deg = 0.5\n", + "size = 0.2\n", "\n", "image_basic_req = {\n", - " 'Service': 'WISE',\n", - " 'Title': 'Wise',\n", - " 'SurveyKey': '3a',\n", - " 'SurveyKeyBand': '2'\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"Wise\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"2\",\n", "}\n", "\n", "hips_basic_req = {\n", - " 'title': 'A HiPS - 0.2',\n", - " 'hipsRootUrl': 'http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1'\n", + " \"title\": \"A HiPS - 0.2\",\n", + " \"hipsRootUrl\": \"http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1\",\n", "}" ] }, @@ -94,12 +96,17 @@ "metadata": {}, "outputs": [], "source": [ - "viewer_id = 'hipsDIV1'\n", - "r = fc.add_cell(0, 0, 4, 2, 'images', viewer_id)\n", - "if r['success']:\n", - " hips_url = 'http://alasky.u-strasbg.fr/DSS/DSSColor';\n", - " status = fc.show_hips(viewer_id=viewer_id, plot_id='aHipsID1-1', hips_root_url = hips_url, \n", - " Title='HiPS-DSS', WorldPt=target)" + "viewer_id = \"hipsDIV1\"\n", + "r = fc.add_cell(0, 0, 4, 2, \"images\", viewer_id)\n", + "if r[\"success\"]:\n", + " hips_url = \"http://alasky.u-strasbg.fr/DSS/DSSColor\"\n", + " status = fc.show_hips(\n", + " viewer_id=viewer_id,\n", + " plot_id=\"aHipsID1-1\",\n", + " hips_root_url=hips_url,\n", + " Title=\"HiPS-DSS\",\n", + " WorldPt=target,\n", + " )" ] }, { @@ -115,7 +122,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.set_color('aHipsID1-1', colormap_id=6, bias=0.6, contrast=1.5)" + "fc.set_color(\"aHipsID1-1\", colormap_id=6, bias=0.6, contrast=1.5)" ] }, { @@ -131,10 +138,10 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "hips_url = 'http://alasky.u-strasbg.fr/DSS/DSS2Merged';\n", - "status = fc.show_hips(plot_id='aHipsID1-2', hips_root_url = hips_url, \n", - " Title='HiPS-DSS2', WorldPt=target)" + "hips_url = \"http://alasky.u-strasbg.fr/DSS/DSS2Merged\"\n", + "status = fc.show_hips(\n", + " plot_id=\"aHipsID1-2\", hips_root_url=hips_url, Title=\"HiPS-DSS2\", WorldPt=target\n", + ")" ] }, { @@ -143,8 +150,8 @@ "metadata": {}, "outputs": [], "source": [ - "hips_url = 'http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1'\n", - "status = fc.show_hips(plot_id='aHipsID2', hips_root_url=hips_url)" + "hips_url = \"http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1\"\n", + "status = fc.show_hips(plot_id=\"aHipsID2\", hips_root_url=hips_url)" ] } ], diff --git a/examples/demo-advanced-all.ipynb b/examples/demo-advanced-all.ipynb index 95991a2..75119a2 100644 --- a/examples/demo-advanced-all.ipynb +++ b/examples/demo-advanced-all.ipynb @@ -23,10 +23,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", + "\n", + "\n", "using_lab = True\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -43,7 +44,7 @@ "outputs": [], "source": [ "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "fc.change_triview_layout( FireflyClient.BIVIEW_T_IChCov)" + "fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov)" ] }, { @@ -52,8 +53,7 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "#os.environ" + "# os.environ" ] }, { @@ -78,11 +78,11 @@ "metadata": {}, "outputs": [], "source": [ - "target = '10.68479;41.26906;EQ_J2000' #Galaxy M31\n", + "target = \"10.68479;41.26906;EQ_J2000\" # Galaxy M31\n", "# other notable targets to try\n", - "#target = '148.88822;69.06529;EQ_J2000' #Galaxy M81\n", - "#target = '202.48417;47.23056;EQ_J2000' #Galaxy M51\n", - "#target = '136.9316774;+1.1195886;galactic' # W5 star-forming region" + "# target = '148.88822;69.06529;EQ_J2000' #Galaxy M81\n", + "# target = '202.48417;47.23056;EQ_J2000' #Galaxy M51\n", + "# target = '136.9316774;+1.1195886;galactic' # W5 star-forming region" ] }, { @@ -107,205 +107,212 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", - "# Add table in cell 'main'. \n", + "# Add table in cell 'main'.\n", "# 'main' is the cell id currently supported by Firefly for element type 'tables'\n", "# this cell is shown at row = 0, col = 4 with width = 2, height = 2\n", "\n", - "r = fc.add_cell(0, 4, 2, 2, 'tables', 'main')\n", + "r = fc.add_cell(0, 4, 2, 2, \"tables\", \"main\")\n", + "\n", + "if r[\"success\"]:\n", + " fc.show_table(\n", + " tbl_id=\"wiseCatTbl\",\n", + " title=\"WISE catalog\",\n", + " target_search_info={\n", + " \"catalogProject\": \"WISE\",\n", + " \"catalog\": \"allwise_p3as_psd\",\n", + " \"position\": target,\n", + " \"SearchMethod\": \"Cone\",\n", + " \"radius\": 1200,\n", + " },\n", + " options={\"removable\": True, \"showUnits\": False, \"showFilters\": True},\n", + " )\n", + "\n", "\n", - "if r['success']:\n", - " \n", - " fc.show_table(tbl_id='wiseCatTbl', title='WISE catalog', \n", - " target_search_info={'catalogProject': 'WISE', 'catalog': 'allwise_p3as_psd',\n", - " 'position': target, 'SearchMethod': 'Cone', 'radius': 1200},\n", - " options={'removable': True, 'showUnits': False, 'showFilters': True})\n", - " \n", - " \n", "# Add first chart - scatter (plot.ly direct plot)\n", - "# in cell 0, 0, 2, 2, \n", - "viewer_id = 'newChartContainer'\n", - "r = fc.add_cell(0, 0, 2, 2,'xyPlots', viewer_id)\n", + "# in cell 0, 0, 2, 2,\n", + "viewer_id = \"newChartContainer\"\n", + "r = fc.add_cell(0, 0, 2, 2, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " trace1 = {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro-w2mpro\",\n", - " 'y': \"tables::w2mpro-w3mpro\",\n", - " 'mode': 'markers',\n", - " 'type': 'scatter', \n", - " 'marker': {'size': 4}}\n", - " trace_data=[trace1]\n", - " \n", - " layout_s = {'title': 'Color-Color', \n", - " 'xaxis': {'title': 'w1mpro-w2mpro (mag)'}, 'yaxis': {'title': 'w2mpro-w3mpro (mag)'}} \n", - " fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data )\n", - " \n", - "# Add second chart - heatmap (plot.ly direct plot) \n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro-w2mpro\",\n", + " \"y\": \"tables::w2mpro-w3mpro\",\n", + " \"mode\": \"markers\",\n", + " \"type\": \"scatter\",\n", + " \"marker\": {\"size\": 4},\n", + " }\n", + " trace_data = [trace1]\n", + "\n", + " layout_s = {\n", + " \"title\": \"Color-Color\",\n", + " \"xaxis\": {\"title\": \"w1mpro-w2mpro (mag)\"},\n", + " \"yaxis\": {\"title\": \"w2mpro-w3mpro (mag)\"},\n", + " }\n", + " fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data)\n", + "\n", + "# Add second chart - heatmap (plot.ly direct plot)\n", "# in cell 2, 0, 2, 3\n", - "viewer_id = 'heatMapContainer'\n", - "r = fc.add_cell(2, 0, 2, 3,'xyPlots', viewer_id)\n", + "viewer_id = \"heatMapContainer\"\n", + "r = fc.add_cell(2, 0, 2, 3, \"xyPlots\", viewer_id)\n", "print(r)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " dataHM = [\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w2',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w2mpro\",\n", - " 'colorscale': 'Blues'\n", - " },\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w3',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w3mpro\",\n", - " 'colorscale': 'Reds',\n", - " 'reversescale': True\n", - " },\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w4',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w4mpro\",\n", - " 'colorscale': 'Greens'\n", - " }]\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w2\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w2mpro\",\n", + " \"colorscale\": \"Blues\",\n", + " },\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w3\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w3mpro\",\n", + " \"colorscale\": \"Reds\",\n", + " \"reversescale\": True,\n", + " },\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w4\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w4mpro\",\n", + " \"colorscale\": \"Greens\",\n", + " },\n", + " ]\n", + "\n", + " layout_hm = {\n", + " \"title\": \"Magnitude-magnitude densities\",\n", + " \"xaxis\": {\"title\": \"w1 photometry (mag)\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + " \"firefly\": {\"xaxis\": {\"min\": 5, \"max\": 20}, \"yaxis\": {\"min\": 4, \"max\": 18}},\n", + " }\n", "\n", - " layout_hm = {'title': 'Magnitude-magnitude densities', \n", - " 'xaxis': {'title': 'w1 photometry (mag)'}, 'yaxis': {'title': ''},\n", - " 'firefly': {'xaxis': {'min': 5, 'max': 20}, 'yaxis': {'min': 4, 'max': 18}}} \n", + " fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM)\n", "\n", - " fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM) \n", - " \n", - "# Add third chart - histogram (plot.ly direct plot) \n", + "# Add third chart - histogram (plot.ly direct plot)\n", "# in cell 2, 2, 2, 3\n", - "viewer_id = 'histContainer'\n", - "r = fc.add_cell(2, 2, 2, 3, 'xyPlots', viewer_id)\n", + "viewer_id = \"histContainer\"\n", + "r = fc.add_cell(2, 2, 2, 3, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " dataH = [\n", " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'w1mpro', \n", - " 'marker': {'color': 'rgba(153, 51, 153, 0.8)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 30,\n", - " 'columnOrExpr': 'w1mpro'\n", - " }\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"w1mpro\",\n", + " \"marker\": {\"color\": \"rgba(153, 51, 153, 0.8)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 30,\n", + " \"columnOrExpr\": \"w1mpro\",\n", + " },\n", " },\n", " },\n", " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'w2mpro', \n", - " 'marker': {'color': 'rgba(102, 153, 0, 0.7)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 40,\n", - " 'columnOrExpr': 'w2mpro' \n", - " }\n", - " }\n", - " }\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"w2mpro\",\n", + " \"marker\": {\"color\": \"rgba(102, 153, 0, 0.7)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 40,\n", + " \"columnOrExpr\": \"w2mpro\",\n", + " },\n", + " },\n", + " },\n", " ]\n", "\n", - " layout_hist = {'title': 'Photometry histogram',\n", - " 'xaxis': {'title': 'photometry (mag)'}, 'yaxis': {'title': ''}} \n", - " result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH ) \n", - " \n", + " layout_hist = {\n", + " \"title\": \"Photometry histogram\",\n", + " \"xaxis\": {\"title\": \"photometry (mag)\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + " }\n", + " result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH)\n", + "\n", "# Add fourth chart - 3D plot (plot.ly direct plot)\n", - "# in cell 2, 4, 2, 3, \n", - "viewer_id = '3dChartContainer'\n", - "r = fc.add_cell(2, 4, 2, 3, 'xyPlots', viewer_id)\n", + "# in cell 2, 4, 2, 3,\n", + "viewer_id = \"3dChartContainer\"\n", + "r = fc.add_cell(2, 4, 2, 3, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " data3d = [\n", " {\n", - " 'type': 'scatter3d',\n", - " 'name': 'w1-w2-w3',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w2mpro\",\n", - " 'z': \"tables::w3mpro\",\n", - " 'mode' : 'markers',\n", - " 'marker' : {\n", - " 'size': 3,\n", - " 'color': 'rgba(127, 127, 127, 1)',\n", - " 'line': {\n", - " 'color': 'rgba(127, 127, 127, 0.5)',\n", - " 'width': 1}\n", + " \"type\": \"scatter3d\",\n", + " \"name\": \"w1-w2-w3\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w2mpro\",\n", + " \"z\": \"tables::w3mpro\",\n", + " \"mode\": \"markers\",\n", + " \"marker\": {\n", + " \"size\": 3,\n", + " \"color\": \"rgba(127, 127, 127, 1)\",\n", + " \"line\": {\"color\": \"rgba(127, 127, 127, 0.5)\", \"width\": 1},\n", " },\n", - " 'hoverinfo': 'x+y+z'\n", - " }]\n", + " \"hoverinfo\": \"x+y+z\",\n", + " }\n", + " ]\n", "\n", - " fsize = {'size': 11}\n", + " fsize = {\"size\": 11}\n", " layout3d = {\n", - " 'title': 'Photometry in band 1, 2, 3',\n", - " 'scene':{\n", - " 'xaxis': {\n", - " 'title': 'w1 (mag)',\n", - " 'titlefont': fsize\n", - " },\n", - " 'yaxis': {\n", - " 'title': 'w2 (mag)',\n", - " 'titlefont': fsize\n", - " },\n", - " 'zaxis': {\n", - " 'title': 'w3 (mag)',\n", - " 'titlefont': fsize\n", - " }\n", - " }\n", + " \"title\": \"Photometry in band 1, 2, 3\",\n", + " \"scene\": {\n", + " \"xaxis\": {\"title\": \"w1 (mag)\", \"titlefont\": fsize},\n", + " \"yaxis\": {\"title\": \"w2 (mag)\", \"titlefont\": fsize},\n", + " \"zaxis\": {\"title\": \"w3 (mag)\", \"titlefont\": fsize},\n", + " },\n", " }\n", - " \n", - " fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d ) \n", - " \n", + "\n", + " fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d)\n", + "\n", "# Add a three color fits\n", "# in cell 0, 2, 2, 2\n", - "viewer_id = '3C'\n", - "r = fc.add_cell(0, 2, 2, 2, 'images', viewer_id)\n", - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "if r['success']:\n", - " threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " }] \n", + "viewer_id = \"3C\"\n", + "r = fc.add_cell(0, 2, 2, 2, \"images\", viewer_id)\n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "if r[\"success\"]:\n", + " threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " ]\n", " fc.show_fits_3color(threeC, viewer_id=viewer_id)" ] }, @@ -315,7 +322,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.change_triview_layout( FireflyClient.BIVIEW_IChCov_T)" + "fc.change_triview_layout(FireflyClient.BIVIEW_IChCov_T)" ] }, { @@ -324,38 +331,39 @@ "metadata": {}, "outputs": [], "source": [ - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " }] \n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + "]\n", "fc.show_fits_3color(threeC)" ] }, diff --git a/examples/demo-advanced-steps.ipynb b/examples/demo-advanced-steps.ipynb index ac27066..cd2cf30 100644 --- a/examples/demo-advanced-steps.ipynb +++ b/examples/demo-advanced-steps.ipynb @@ -23,11 +23,14 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = True\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -37,7 +40,7 @@ "outputs": [], "source": [ "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "fc.change_triview_layout( FireflyClient.BIVIEW_T_IChCov)" + "fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov)" ] }, { @@ -78,13 +81,23 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl\",\n", - " timeout=120, cache=True)\n", - "meta_info = {'datasetInfoConverterId': 'SimpleMoving','positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000',\n", - " 'datasource': 'image_url'}\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='movingtbl', title='A moving object table', page_size=15, meta=meta_info)\n", - " " + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "meta_info = {\n", + " \"datasetInfoConverterId\": \"SimpleMoving\",\n", + " \"positionCoordColumns\": \"ra_obj;dec_obj;EQ_J2000\",\n", + " \"datasource\": \"image_url\",\n", + "}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " tbl_id=\"movingtbl\",\n", + " title=\"A moving object table\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -100,9 +113,17 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl',\n", - " timeout=120, cache=True)\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='tbl_chart', title='table for xyplot and histogram', page_size=15)" + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " tbl_id=\"tbl_chart\",\n", + " title=\"table for xyplot and histogram\",\n", + " page_size=15,\n", + ")" ] }, { @@ -118,10 +139,18 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl\", \n", - " timeout=120, cache=True)\n", - "meta_info = {'datasource': 'DP'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of simple images', page_size=15, meta=meta_info)" + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "meta_info = {\"datasource\": \"DP\"}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " title=\"A table of simple images\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -137,11 +166,19 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl\", \n", - " timeout=120, cache=True)\n", + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "\n", - "meta_info = {'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', 'datasource': 'FITS'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of m31 images', page_size=15, meta=meta_info)" + "meta_info = {\"positionCoordColumns\": \"ra_obj;dec_obj;EQ_J2000\", \"datasource\": \"FITS\"}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " title=\"A table of m31 images\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -174,11 +211,11 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "image_name = astropy.utils.data.download_file('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' +\n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", - "fc.show_fits(file_on_server=fc.upload_file(image_name), title='WISE Cutout')\n", - " " + "image_name = astropy.utils.data.download_file(\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", + "fc.show_fits(file_on_server=fc.upload_file(image_name), title=\"WISE Cutout\")" ] }, { @@ -194,20 +231,38 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "fc.show_fits(plot_id='m49025b_143_2', OverlayPosition='330.347003;-2.774482;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49025b143-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49273b_134_2', OverlayPosition='333.539702;-0.779310;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49273b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49277b_135_1', OverlayPosition='333.589054;-0.747251;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49277b135-w1', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits')\n", - "fc.show_fits(plot_id='m49289b_134_2', OverlayPosition='333.736578;-0.651222;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49289b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits')\n", - " " + "fc.show_fits(\n", + " plot_id=\"m49025b_143_2\",\n", + " OverlayPosition=\"330.347003;-2.774482;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49025b143-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49273b_134_2\",\n", + " OverlayPosition=\"333.539702;-0.779310;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49273b134-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49277b_135_1\",\n", + " OverlayPosition=\"333.589054;-0.747251;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49277b135-w1\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49289b_134_2\",\n", + " OverlayPosition=\"333.736578;-0.651222;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49289b134-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits\",\n", + ")" ] }, { @@ -226,19 +281,22 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "trace1 = {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'x': \"tables::ra1\",\n", - " 'y': \"tables::dec1\",\n", - " 'mode': 'markers',\n", - " 'type': 'scatter', \n", - " 'marker': {'size': 4}}\n", - "trace_data=[trace1]\n", - " \n", - "layout_s = {'title': 'Coordinates', \n", - " 'xaxis': {'title': 'ra1 (deg)'}, 'yaxis': {'title': 'dec1 (deg)'}} \n", - "fc.show_chart( layout=layout_s, data=trace_data ) " + " \"tbl_id\": \"tbl_chart\",\n", + " \"x\": \"tables::ra1\",\n", + " \"y\": \"tables::dec1\",\n", + " \"mode\": \"markers\",\n", + " \"type\": \"scatter\",\n", + " \"marker\": {\"size\": 4},\n", + "}\n", + "trace_data = [trace1]\n", + "\n", + "layout_s = {\n", + " \"title\": \"Coordinates\",\n", + " \"xaxis\": {\"title\": \"ra1 (deg)\"},\n", + " \"yaxis\": {\"title\": \"dec1 (deg)\"},\n", + "}\n", + "fc.show_chart(layout=layout_s, data=trace_data)" ] }, { @@ -256,27 +314,29 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "histData = [\n", - " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'magzp', \n", - " 'marker': {'color': 'rgba(153, 51, 153, 0.8)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 30,\n", - " 'columnOrExpr': 'magzp'\n", - " }\n", + " {\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"magzp\",\n", + " \"marker\": {\"color\": \"rgba(153, 51, 153, 0.8)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"tbl_chart\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 30,\n", + " \"columnOrExpr\": \"magzp\",\n", " },\n", - " }\n", - " ]\n", + " },\n", + " }\n", + "]\n", "\n", - "layout_hist = {'title': 'Magnitude Zeropoints',\n", - " 'xaxis': {'title': 'magzp'}, 'yaxis': {'title': ''}} \n", - "result = fc.show_chart(layout=layout_hist, data=histData ) " + "layout_hist = {\n", + " \"title\": \"Magnitude Zeropoints\",\n", + " \"xaxis\": {\"title\": \"magzp\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + "}\n", + "result = fc.show_chart(layout=layout_hist, data=histData)" ] }, { @@ -311,8 +371,11 @@ "metadata": {}, "outputs": [], "source": [ - "img_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits', \n", - " timeout=120, cache=True)\n", + "img_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "fc.show_fits(file_on_server=fc.upload_file(img_name))" ] }, @@ -322,7 +385,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(plot_id='zzz', file_on_server=fc.upload_file(img_name))" + "fc.show_fits(plot_id=\"zzz\", file_on_server=fc.upload_file(img_name))" ] }, { @@ -338,9 +401,17 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(plot_id='xxq', Service='TWOMASS', Title='2mass from service', ZoomType='LEVEL',\n", - " initZoomLevel=2, SurveyKey='asky', SurveyKeyBand='k',\n", - " WorldPt='10.68479;41.26906;EQ_J2000', SizeInDeg='.12')" + "fc.show_fits(\n", + " plot_id=\"xxq\",\n", + " Service=\"TWOMASS\",\n", + " Title=\"2mass from service\",\n", + " ZoomType=\"LEVEL\",\n", + " initZoomLevel=2,\n", + " SurveyKey=\"asky\",\n", + " SurveyKeyBand=\"k\",\n", + " WorldPt=\"10.68479;41.26906;EQ_J2000\",\n", + " SizeInDeg=\".12\",\n", + ")" ] } ], diff --git a/examples/demo-advanced-table-images.ipynb b/examples/demo-advanced-table-images.ipynb index dbcb27f..0f8194c 100644 --- a/examples/demo-advanced-table-images.ipynb +++ b/examples/demo-advanced-table-images.ipynb @@ -31,9 +31,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -115,8 +117,8 @@ "metadata": {}, "outputs": [], "source": [ - "# first table in cell 'main' \n", - "fs.load_moving_table(0,0,4,2, fc)" + "# first table in cell 'main'\n", + "fs.load_moving_table(0, 0, 4, 2, fc)" ] }, { @@ -125,7 +127,7 @@ "metadata": {}, "outputs": [], "source": [ - "# add table in cell 'main' for chart and histogram \n", + "# add table in cell 'main' for chart and histogram\n", "fs.add_table_for_chart(fc)" ] }, @@ -163,7 +165,7 @@ "outputs": [], "source": [ "# show cell containing the image from the active table with datasource column in cell 'main'\n", - "fs.load_image_metadata(2, 0, 4, 2, fc, 'image-meta')" + "fs.load_image_metadata(2, 0, 4, 2, fc, \"image-meta\")" ] }, { @@ -173,7 +175,7 @@ "outputs": [], "source": [ "# show cell containing FITS in cell 'wise-cutout'\n", - "fs.load_image(0, 4, 2, 2, fc, 'wise-cutout') # load an image" + "fs.load_image(0, 4, 2, 2, fc, \"wise-cutout\") # load an image" ] }, { @@ -183,7 +185,7 @@ "outputs": [], "source": [ "# show cell with 4 FITS of moving objects in cell 'movingStff'\n", - "fs.load_moving(2, 4, 2, 2, fc, 'movingStuff')" + "fs.load_moving(2, 4, 2, 2, fc, \"movingStuff\")" ] }, { @@ -200,7 +202,7 @@ "outputs": [], "source": [ "# show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main'\n", - "fs.load_xy(4, 0, 2, 3, fc, 'chart-cell-xy')" + "fs.load_xy(4, 0, 2, 3, fc, \"chart-cell-xy\")" ] }, { @@ -227,7 +229,7 @@ "outputs": [], "source": [ "# show cell containing coverage image associated with the active table in cell 'main'\n", - "fs.load_coverage_image(4, 4, 3, 3, fc, 'image-coverage')" + "fs.load_coverage_image(4, 4, 3, 3, fc, \"image-coverage\")" ] }, { @@ -246,7 +248,7 @@ "metadata": {}, "outputs": [], "source": [ - "# show second image in random location. This image is located in the same cell as the previous one \n", + "# show second image in random location. This image is located in the same cell as the previous one\n", "fs.load_second_image_in_random(fc)" ] } diff --git a/examples/demo-advanced-tables-images-upload.ipynb b/examples/demo-advanced-tables-images-upload.ipynb index 1825c59..5464b37 100644 --- a/examples/demo-advanced-tables-images-upload.ipynb +++ b/examples/demo-advanced-tables-images-upload.ipynb @@ -31,10 +31,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -105,8 +106,8 @@ "metadata": {}, "outputs": [], "source": [ - "# first table in cell 'main' \n", - "fs.load_moving_table(0,0,4,2, fc)" + "# first table in cell 'main'\n", + "fs.load_moving_table(0, 0, 4, 2, fc)" ] }, { @@ -115,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "# add table in cell 'main' for chart and histogram \n", + "# add table in cell 'main' for chart and histogram\n", "fs.add_table_for_chart(fc)" ] }, @@ -145,8 +146,8 @@ "metadata": {}, "outputs": [], "source": [ - "f= '/Users/roby/fits/2mass-m31-2412rows.tbl'\n", - "file= fc.upload_file(f);" + "f = \"/Users/roby/fits/2mass-m31-2412rows.tbl\"\n", + "file = fc.upload_file(f);" ] }, { @@ -155,7 +156,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table(file)\n" + "fc.show_table(file)" ] }, { @@ -164,7 +165,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table('${cache-dir}/upload_2482826742890803252.fits')\n" + "fc.show_table(\"${cache-dir}/upload_2482826742890803252.fits\")" ] }, { @@ -181,7 +182,7 @@ "outputs": [], "source": [ "# show cell containing the image from the active table with datasource column in cell 'main'\n", - "fs.load_image_metadata(2, 0, 4, 2, fc, 'image-meta')" + "fs.load_image_metadata(2, 0, 4, 2, fc, \"image-meta\")" ] }, { @@ -191,7 +192,7 @@ "outputs": [], "source": [ "# show cell containing FITS in cell 'wise-cutout'\n", - "fs.load_image(0, 4, 2, 2, fc, 'wise-cutout') # load an image" + "fs.load_image(0, 4, 2, 2, fc, \"wise-cutout\") # load an image" ] }, { @@ -201,7 +202,7 @@ "outputs": [], "source": [ "# show cell with 4 FITS of moving objects in cell 'movingStff'\n", - "fs.load_moving(2, 4, 2, 2, fc, 'movingStuff')" + "fs.load_moving(2, 4, 2, 2, fc, \"movingStuff\")" ] }, { @@ -218,7 +219,7 @@ "outputs": [], "source": [ "# show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main'\n", - "fs.load_xy(4, 0, 2, 3, fc, 'chart-cell-xy')" + "fs.load_xy(4, 0, 2, 3, fc, \"chart-cell-xy\")" ] }, { @@ -245,7 +246,7 @@ "outputs": [], "source": [ "# show cell containing coverage image associated with the active table in cell 'main'\n", - "fs.load_coverage_image(4, 4, 3, 3, fc, 'image-coverage')" + "fs.load_coverage_image(4, 4, 3, 3, fc, \"image-coverage\")" ] }, { @@ -264,7 +265,7 @@ "metadata": {}, "outputs": [], "source": [ - "# show second image in random location. This image is located in the same cell as the previous one \n", + "# show second image in random location. This image is located in the same cell as the previous one\n", "fs.load_second_image_in_random(fc)" ] } diff --git a/examples/demo-basic.ipynb b/examples/demo-basic.ipynb index 343fe79..f83bdcb 100644 --- a/examples/demo-basic.ipynb +++ b/examples/demo-basic.ipynb @@ -36,12 +36,15 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "#url = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "# url = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -94,8 +97,10 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = ('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", + "image_url = (\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)" ] }, @@ -112,9 +117,11 @@ "metadata": {}, "outputs": [], "source": [ - "table_url = (\"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&\" +\n", - " \"QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),\" +\n", - " \"CIRCLE('J2000',70.0,20.0,0.1))=1\")\n", + "table_url = (\n", + " \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&\"\n", + " + \"QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),\"\n", + " + \"CIRCLE('J2000',70.0,20.0,0.1))=1\"\n", + ")\n", "tablename = astropy.utils.data.download_file(table_url, timeout=120, cache=True)" ] }, @@ -190,7 +197,7 @@ "outputs": [], "source": [ "imval = fc.upload_file(filename)\n", - "status = fc.show_fits(file_on_server=imval, plot_id=\"wise-cutout\", title='WISE Cutout')" + "status = fc.show_fits(file_on_server=imval, plot_id=\"wise-cutout\", title=\"WISE Cutout\")" ] }, { @@ -206,9 +213,12 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.show_fits(file_on_server=None, plot_id=\"wise-fullimage\", \n", - " URL='http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits')" + "status = fc.show_fits(\n", + " file_on_server=None,\n", + " plot_id=\"wise-fullimage\",\n", + " URL=\"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits\",\n", + ")" ] }, { @@ -224,8 +234,8 @@ "metadata": {}, "outputs": [], "source": [ - "file= fc.upload_file(tablename)\n", - "status = fc.show_table(file, tbl_id='tablemass', title='My 2MASS Catalog', page_size=50)" + "file = fc.upload_file(tablename)\n", + "status = fc.show_table(file, tbl_id=\"tablemass\", title=\"My 2MASS Catalog\", page_size=50)" ] }, { @@ -241,7 +251,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.show_xyplot(tbl_id='tablemass', xCol='j_m', yCol='h_m-k_m')" + "status = fc.show_xyplot(tbl_id=\"tablemass\", xCol=\"j_m\", yCol=\"h_m-k_m\")" ] }, { @@ -257,7 +267,7 @@ "metadata": {}, "outputs": [], "source": [ - "# trace0 = {'tbl_id': 'tablemass', 'x': \"tables::j_m\", 'y': \"tables::h_m-k_m\", \n", + "# trace0 = {'tbl_id': 'tablemass', 'x': \"tables::j_m\", 'y': \"tables::h_m-k_m\",\n", "# 'type' : 'scatter', 'mode': 'markers'}\n", "# status = fc.show_chart(data=[trace0])" ] @@ -275,7 +285,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_zoom('wise-fullimage', 2)" + "status = fc.set_zoom(\"wise-fullimage\", 2)" ] }, { @@ -291,7 +301,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_pan('wise-fullimage', x=70, y=20, coord='J2000')" + "status = fc.set_pan(\"wise-fullimage\", x=70, y=20, coord=\"J2000\")" ] }, { @@ -307,7 +317,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_stretch('wise-fullimage', stype='zscale', algorithm='linear')" + "status = fc.set_stretch(\"wise-fullimage\", stype=\"zscale\", algorithm=\"linear\")" ] }, { @@ -323,7 +333,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_color('wise-fullimage', colormap_id=6, bias=0.6, contrast=1.5)" + "status = fc.set_color(\"wise-fullimage\", colormap_id=6, bias=0.6, contrast=1.5)" ] }, { @@ -339,10 +349,13 @@ "metadata": {}, "outputs": [], "source": [ - "my_regions= ['image;polygon 125 25 160 195 150 150 #color=cyan',\n", - " 'icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}']\n", - "status = fc.add_region_data(region_data=my_regions, region_layer_id='layer1',\n", - " plot_id='wise-cutout')" + "my_regions = [\n", + " \"image;polygon 125 25 160 195 150 150 #color=cyan\",\n", + " \"icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}\",\n", + "]\n", + "status = fc.add_region_data(\n", + " region_data=my_regions, region_layer_id=\"layer1\", plot_id=\"wise-cutout\"\n", + ")" ] }, { @@ -358,7 +371,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.remove_region_data(region_data=my_regions[1], region_layer_id='layer1')" + "fc.remove_region_data(region_data=my_regions[1], region_layer_id=\"layer1\")" ] }, { diff --git a/examples/demo-lsst-footprint.ipynb b/examples/demo-lsst-footprint.ipynb index 2b63fab..9257ae9 100644 --- a/examples/demo-lsst-footprint.ipynb +++ b/examples/demo-lsst-footprint.ipynb @@ -36,11 +36,14 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -86,7 +89,9 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = 'http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits'\n", + "image_url = (\n", + " \"http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)\n", "imval = fc.upload_file(filename)" ] @@ -104,8 +109,10 @@ "metadata": {}, "outputs": [], "source": [ - "plotid = 'footprinttest'\n", - "status = fc.show_fits(file_on_server=imval, plot_id=plotid, title='footprints HSC R-band')" + "plotid = \"footprinttest\"\n", + "status = fc.show_fits(\n", + " file_on_server=imval, plot_id=plotid, title=\"footprints HSC R-band\"\n", + ")" ] }, { @@ -128,7 +135,7 @@ "metadata": {}, "outputs": [], "source": [ - "table_url = 'http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml'\n", + "table_url = \"http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml\"\n", "footprint_table = astropy.utils.data.download_file(table_url, cache=True, timeout=120)\n", "tableval = fc.upload_file(footprint_table)" ] @@ -146,13 +153,16 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.overlay_footprints(tableval, title='footprints HSC R-band',\n", - " footprint_layer_id='footprint_layer_1', \n", - " plot_id=plotid, \n", - " highlightColor='yellow', \n", - " selectColor='cyan', \n", - " style='fill', \n", - " color='rgba(74,144,226,0.30)')" + "status = fc.overlay_footprints(\n", + " tableval,\n", + " title=\"footprints HSC R-band\",\n", + " footprint_layer_id=\"footprint_layer_1\",\n", + " plot_id=plotid,\n", + " highlightColor=\"yellow\",\n", + " selectColor=\"cyan\",\n", + " style=\"fill\",\n", + " color=\"rgba(74,144,226,0.30)\",\n", + ")" ] } ], diff --git a/examples/demo-region.ipynb b/examples/demo-region.ipynb index 25d3679..323086d 100644 --- a/examples/demo-region.ipynb +++ b/examples/demo-region.ipynb @@ -36,11 +36,14 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -95,8 +98,10 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = ('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", + "image_url = (\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)" ] }, @@ -153,7 +158,9 @@ "outputs": [], "source": [ "imval = fc.upload_file(filename)\n", - "status = fc.show_fits(file_on_server=imval, plot_id=\"region test\", title='text region test')" + "status = fc.show_fits(\n", + " file_on_server=imval, plot_id=\"region test\", title=\"text region test\"\n", + ")" ] }, { @@ -169,14 +176,16 @@ "metadata": {}, "outputs": [], "source": [ - "text_regions= [\n", + "text_regions = [\n", " 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font=\"Times 16 bold normal\" textangle=30',\n", " 'image;text 100 25 # color=pink text={text angle is -20 deg } font=\"Times 16 bold italic\" textangle=-20',\n", - " 'circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan',\n", - " 'image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}',\n", - " 'circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and \\' in it}']\n", - "status = fc.add_region_data(region_data=text_regions, region_layer_id='layerTextRegion',\n", - " plot_id='region test')" + " \"circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan\",\n", + " \"image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}\",\n", + " \"circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \\\" and ' in it}\",\n", + "]\n", + "status = fc.add_region_data(\n", + " region_data=text_regions, region_layer_id=\"layerTextRegion\", plot_id=\"region test\"\n", + ")" ] } ], diff --git a/examples/demo-show-image.ipynb b/examples/demo-show-image.ipynb index 9c42e65..087372b 100644 --- a/examples/demo-show-image.ipynb +++ b/examples/demo-show-image.ipynb @@ -7,9 +7,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -18,8 +20,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "\n" + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" ] }, { @@ -28,7 +29,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(URL='http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits')\n" + "fc.show_fits(URL=\"http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits\")" ] } ], diff --git a/examples/filetable.py b/examples/filetable.py index 32b031e..04fafdd 100755 --- a/examples/filetable.py +++ b/examples/filetable.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -from argparse import ArgumentParser, HelpFormatter -import csv -from glob import glob import os +import csv import tempfile import textwrap +from glob import glob +from argparse import HelpFormatter, ArgumentParser import firefly_client diff --git a/examples/firefly_slate_demo.py b/examples/firefly_slate_demo.py index 771d003..5bfbe37 100644 --- a/examples/firefly_slate_demo.py +++ b/examples/firefly_slate_demo.py @@ -6,135 +6,214 @@ def download_from(url): def load_moving_table(row, col, width, height, fc, cell_id=None): - moving_tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl") - - r = fc.add_cell(row, col, width, height, 'tables', cell_id) - meta_info = {'datasetInfoConverterId': 'SimpleMoving', - 'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', - 'datasource': 'image_url'} + moving_tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl" + ) + + r = fc.add_cell(row, col, width, height, "tables", cell_id) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } tbl_name = fc.upload_file(moving_tbl_name) - if r['success']: - fc.show_table(tbl_name, tbl_id='movingtbl', title='A moving object table', - page_size=15, meta=meta_info) + if r["success"]: + fc.show_table( + tbl_name, + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) def add_simple_m31_image_table(fc): - tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl") - - meta_info = {'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', - 'datasource': 'FITS'} - fc.show_table(fc.upload_file(tbl_name), title='A table of m31 images', page_size=15, meta=meta_info) + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl" + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) def add_simple_image_table(fc): - tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl") + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl" + ) - meta_info = {'datasource': 'FITS'} - fc.show_table(fc.upload_file(tbl_name), title='A table of simple images', page_size=15, meta=meta_info) + meta_info = {"datasource": "FITS"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) def add_table_for_chart(fc): - tbl_name = download_from('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl') + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl" + ) - fc.show_table(fc.upload_file(tbl_name), tbl_id='tbl_chart', title='table for xyplot and histogram', page_size=15) + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) def load_image(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'images', cell_id) + r = fc.add_cell(row, col, width, height, "images", cell_id) - image_name = download_from('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + - '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix') + image_name = download_from( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) - if r['success']: - fc.show_fits(file_on_server=fc.upload_file(image_name), viewer_id=r['cell_id'], title='WISE Cutout') + if r["success"]: + fc.show_fits( + file_on_server=fc.upload_file(image_name), + viewer_id=r["cell_id"], + title="WISE Cutout", + ) def load_image_metadata(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'tableImageMeta', cell_id) + r = fc.add_cell(row, col, width, height, "tableImageMeta", cell_id) - if r['success']: - fc.show_image_metadata(viewer_id=r['cell_id']) + if r["success"]: + fc.show_image_metadata(viewer_id=r["cell_id"]) def load_coverage_image(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'coverageImage', cell_id) + r = fc.add_cell(row, col, width, height, "coverageImage", cell_id) - if r['success']: - fc.show_coverage(viewer_id=r['cell_id']) + if r["success"]: + fc.show_coverage(viewer_id=r["cell_id"]) def load_moving(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'images', cell_id) - - if r['success']: - v_id = r['cell_id'] - fc.show_fits(plot_id='m49025b_143_2', viewer_id=v_id, OverlayPosition='330.347003;-2.774482;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49025b143-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits') - fc.show_fits(plot_id='m49273b_134_2', viewer_id=v_id, OverlayPosition='333.539702;-0.779310;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49273b134-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits') - fc.show_fits(plot_id='m49277b_135_1', viewer_id=v_id, OverlayPosition='333.589054;-0.747251;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49277b135-w1', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits') - fc.show_fits(plot_id='m49289b_134_2', viewer_id=v_id, OverlayPosition='333.736578;-0.651222;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49289b134-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits') + r = fc.add_cell(row, col, width, height, "images", cell_id) + + if r["success"]: + v_id = r["cell_id"] + fc.show_fits( + plot_id="m49025b_143_2", + viewer_id=v_id, + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + viewer_id=v_id, + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + viewer_id=v_id, + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + viewer_id=v_id, + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) def load_xy(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'xyPlots', cell_id) + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) - if r['success']: + if r["success"]: trace1 = { - 'tbl_id': 'tbl_chart', - 'x': "tables::ra1", - 'y': "tables::dec1", - 'mode': 'markers', - 'type': 'scatter', - 'marker': {'size': 4}} - trace_data=[trace1] - - layout_s = {'title': 'Coordinates', - 'xaxis': {'title': 'ra1 (deg)'}, 'yaxis': {'title': 'dec1 (deg)'}} - fc.show_chart(group_id=r['cell_id'], layout=layout_s, data=trace_data ) + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(group_id=r["cell_id"], layout=layout_s, data=trace_data) def load_histogram(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'xyPlots', cell_id) + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) - if r['success']: + if r["success"]: histData = [ { - 'type': 'fireflyHistogram', - 'name': 'magzp', - 'marker': {'color': 'rgba(153, 51, 153, 0.8)'}, - 'firefly': { - 'tbl_id': 'tbl_chart', - 'options': { - 'algorithm': 'fixedSizeBins', - 'fixedBinSizeSelection': 'numBins', - 'numBins': 30, - 'columnOrExpr': 'magzp' - } + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, }, } ] - layout_hist = {'title': 'Magnitude Zeropoints', - 'xaxis': {'title': 'magzp'}, 'yaxis': {'title': ''}} - result = fc.show_chart(group_id=r['cell_id'], layout=layout_hist, data=histData ) + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=r["cell_id"], layout=layout_hist, data=histData) + assert result is not None def load_first_image_in_random(fc): - img_name = download_from('http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits') + img_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits" + ) fc.show_fits(file_on_server=fc.upload_file(img_name)) def load_second_image_in_random(fc): - fc.show_fits(plot_id='xxq', Service='TWOMASS', Title='2mass from service', ZoomType='LEVEL', - initZoomLevel=2, SurveyKey='asky', SurveyKeyBand='k', - WorldPt='10.68479;41.26906;EQ_J2000', SizeInDeg='.12') - + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/examples/multi-moc.py b/examples/multi-moc.py index b046570..341d5d8 100644 --- a/examples/multi-moc.py +++ b/examples/multi-moc.py @@ -1,15 +1,23 @@ import astropy.utils.data + from firefly_client import FireflyClient -fc = FireflyClient.make_client('http://127.0.0.1:8080/firefly', channel_override='moc-channel') -print('firefly url: {}'.format(fc.get_firefly_url())) -mocs = ['galex.fits', 'hershel.fits', 'nicmos.fits'] -meta = {'PREFERRED_HIPS': 'https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color'} + +fc = FireflyClient.make_client( + "http://127.0.0.1:8080/firefly", channel_override="moc-channel" +) +print("firefly url: {}".format(fc.get_firefly_url())) +mocs = ["galex.fits", "hershel.fits", "nicmos.fits"] + +meta = {"PREFERRED_HIPS": "https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color"} for m in mocs: - result = input('load {}? (y or n): '.format(m)) - if result == 'y': - downloadName = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/moc/{}'.format(m), - timeout=120, cache=True) - serverFile= fc.upload_file(downloadName) + result = input("load {}? (y or n): ".format(m)) + if result == "y": + downloadName = astropy.utils.data.download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/moc/{}".format(m), + timeout=120, + cache=True, + ) + serverFile = fc.upload_file(downloadName) fc.fetch_table(serverFile, m, title=m, meta=meta) print(m) diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index 881a65b..e49c184 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -39,9 +39,11 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import firefly_client.plot as ffplt\n", - "fc = FireflyClient.make_client('https://irsa.ipac.caltech.edu/irsaviewer')\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", + "fc = FireflyClient.make_client(\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", "ffplt.use_client(fc)" ] }, @@ -112,9 +114,9 @@ "metadata": {}, "outputs": [], "source": [ - "#import os\n", - "#my_channel = os.environ['USER'] + '-plot-module'\n", - "#ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel)" + "# import os\n", + "# my_channel = os.environ['USER'] + '-plot-module'\n", + "# ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel)" ] }, { @@ -179,8 +181,10 @@ "outputs": [], "source": [ "m31_table_fname = astropy.utils.data.download_file(\n", - " 'http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl',\n", - " timeout=120, cache=True)" + " \"http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")" ] }, { @@ -196,7 +200,7 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_id = ffplt.upload_table(m31_table_fname, title='M31 2MASS', view_coverage=True)" + "tbl_id = ffplt.upload_table(m31_table_fname, title=\"M31 2MASS\", view_coverage=True)" ] }, { @@ -212,7 +216,7 @@ "metadata": {}, "outputs": [], "source": [ - "ffplt.hist('j_m')" + "ffplt.hist(\"j_m\")" ] }, { @@ -228,7 +232,7 @@ "metadata": {}, "outputs": [], "source": [ - "ffplt.scatter('h_m - k_m', 'j_m')" + "ffplt.scatter(\"h_m - k_m\", \"j_m\")" ] }, { @@ -263,9 +267,13 @@ ], "source": [ "import astropy.io.fits as fits\n", + "\n", + "\n", "m31_image_fname = astropy.utils.data.download_file(\n", - " 'http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits',\n", - " timeout=120, cache=True)\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "hdu = fits.open(m31_image_fname)\n", "type(hdu)" ] @@ -379,8 +387,11 @@ "metadata": {}, "outputs": [], "source": [ - "wise_tbl_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl',\n", - " timeout=120, cache=True)" + "wise_tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")" ] }, { @@ -397,7 +408,9 @@ "outputs": [], "source": [ "from astropy.table import Table\n", - "wise_table = Table.read(wise_tbl_name, format='ipac')" + "\n", + "\n", + "wise_table = Table.read(wise_tbl_name, format=\"ipac\")" ] }, { @@ -424,7 +437,7 @@ } ], "source": [ - "ffplt.upload_table(wise_table, title='WISE Demo Table')" + "ffplt.upload_table(wise_table, title=\"WISE Demo Table\")" ] }, { diff --git a/firefly_client/__init__.py b/firefly_client/__init__.py index 30fe29e..89e61a6 100644 --- a/firefly_client/__init__.py +++ b/firefly_client/__init__.py @@ -1,12 +1,15 @@ from importlib.metadata import PackageNotFoundError, version -from .firefly_client import FireflyClient -from .ffws import FFWs from .env import Env +from .ffws import FFWs from .range_values import RangeValues +from .firefly_client import FireflyClient + try: __version__ = version("firefly_client") except PackageNotFoundError: # package is not installed __version__ = None + +__all__ = ["Env", "FFWs", "FireflyClient", "RangeValues"] diff --git a/firefly_client/env.py b/firefly_client/env.py index 0f552d0..ede90ee 100644 --- a/firefly_client/env.py +++ b/firefly_client/env.py @@ -1,63 +1,89 @@ -import base64 import os import uuid +import base64 import datetime + try: from .fc_utils import str_2_bool except ImportError: from fc_utils import str_2_bool # environment variable list -ENV_FF_LAB_EXT = 'fireflyLabExtension' -ENV_FF_CHANNEL_LAB = 'fireflyChannelLab' -ENV_FF_URL_LAB = 'fireflyURLLab' -ENV_FF_URL = 'FIREFLY_URL' -ENV_FF_CHANNEL = 'FIREFLY_CHANNEL' -ENV_FF_HTML = 'FIREFLY_HTML' -ENV_USER = 'USER' +ENV_FF_LAB_EXT = "fireflyLabExtension" +ENV_FF_CHANNEL_LAB = "fireflyChannelLab" +ENV_FF_URL_LAB = "fireflyURLLab" +ENV_FF_URL = "FIREFLY_URL" +ENV_FF_CHANNEL = "FIREFLY_CHANNEL" +ENV_FF_HTML = "FIREFLY_HTML" +ENV_USER = "USER" -EXT_INCORRECT = 'jupyter_firefly_extensions appears to be installed incorrectly.' -SUGGESTION = 'fix jupyter_firefly_extensions in Jupyter Lab or use FireflyClient.make_client()' -COULD_NO_FIND_ENV = 'Could not find environment variable ' +EXT_INCORRECT = "jupyter_firefly_extensions appears to be installed incorrectly." +SUGGESTION = ( + "fix jupyter_firefly_extensions in Jupyter Lab or use FireflyClient.make_client()" +) +COULD_NO_FIND_ENV = "Could not find environment variable " class Env: # all os environment access here - firefly_lab_extension = str_2_bool(os.environ.get(ENV_FF_LAB_EXT, '')) + firefly_lab_extension = str_2_bool(os.environ.get(ENV_FF_LAB_EXT, "")) firefly_channel_lab = os.environ.get(ENV_FF_CHANNEL_LAB) firefly_url_lab = os.environ.get(ENV_FF_URL_LAB) firefly_url = os.environ.get(ENV_FF_URL) firefly_channel_from_env = os.environ.get(ENV_FF_CHANNEL) - firefly_html = os.environ.get(ENV_FF_HTML, '') - user = os.environ.get(ENV_USER, '') + firefly_html = os.environ.get(ENV_FF_HTML, "") + user = os.environ.get(ENV_USER, "") @classmethod def validate_lab_client(cls, generate_lab_ext_channel): - """ return the url and channel or raise an Error """ + """return the url and channel or raise an Error""" not cls.lab_ext_valid() and cls.raise_invalid_lab_error() return cls.firefly_url_lab, cls.resolve_lab_channel(generate_lab_ext_channel) @classmethod def raise_invalid_lab_error(cls): if not cls.firefly_lab_extension: - raise RuntimeError('FireflyClient.makeLabClient can only be used in the Jupyterlab environment. ' + - SUGGESTION) + raise RuntimeError( + "FireflyClient.makeLabClient can only be used in the Jupyterlab environment. " + + SUGGESTION + ) if not cls.firefly_channel_lab: - raise RuntimeError(COULD_NO_FIND_ENV + ENV_FF_CHANNEL_LAB + '. ' + EXT_INCORRECT + ' ' + SUGGESTION) + raise RuntimeError( + COULD_NO_FIND_ENV + + ENV_FF_CHANNEL_LAB + + ". " + + EXT_INCORRECT + + " " + + SUGGESTION + ) if not cls.firefly_url_lab: - raise RuntimeError(COULD_NO_FIND_ENV + ENV_FF_URL_LAB + '. ' + EXT_INCORRECT + ' ' + SUGGESTION) + raise RuntimeError( + COULD_NO_FIND_ENV + + ENV_FF_URL_LAB + + ". " + + EXT_INCORRECT + + " " + + SUGGESTION + ) @classmethod def show_start_browser_tab_msg(cls, url): - print('Firefly URL is {}'.format(url)) - print('To start a new tab you you will have to disable popup blocking for this site.') - print(' Chrome: look at the right side of the address bar') - print(' Firefox: a preference bar appears at the top') - print(' Safari: shows an animation to follow on left side bar') + print("Firefly URL is {}".format(url)) + print( + "To start a new tab you you will have to disable popup blocking for this site." + ) + print(" Chrome: look at the right side of the address bar") + print(" Firefox: a preference bar appears at the top") + print(" Safari: shows an animation to follow on left side bar") @classmethod - def lab_ext_valid(cls): return bool(cls.firefly_lab_extension and cls.firefly_channel_lab and cls.firefly_url_lab) + def lab_ext_valid(cls): + return bool( + cls.firefly_lab_extension + and cls.firefly_channel_lab + and cls.firefly_url_lab + ) @classmethod def resolve_client_channel(cls, in_channel): @@ -66,14 +92,16 @@ def resolve_client_channel(cls, in_channel): elif cls.firefly_channel_from_env: return cls.firefly_channel_from_env else: - start_str = cls.user + datetime.datetime.today().strftime('%Y-%m-%d') - return base64.urlsafe_b64encode(start_str.encode()).decode().replace('=', '') + start_str = cls.user + datetime.datetime.today().strftime("%Y-%m-%d") + return ( + base64.urlsafe_b64encode(start_str.encode()).decode().replace("=", "") + ) @classmethod def resolve_lab_channel(cls, generate_lab_ext_channel): if cls.lab_ext_valid(): if generate_lab_ext_channel: - return cls.firefly_channel_lab + '__lab-external__viewer' + return cls.firefly_channel_lab + "__lab-external__viewer" else: return cls.firefly_channel_lab else: @@ -86,19 +114,25 @@ def find_default_firefly_url(cls): elif cls.firefly_url: return cls.firefly_url else: - return 'http://localhost:8080/firefly' + return "http://localhost:8080/firefly" @classmethod - def find_default_firefly_html(cls): return cls.firefly_html + def find_default_firefly_html(cls): + return cls.firefly_html @classmethod def failed_net_message(cls, location, status_code=-1): - s_str = 'with status: %s' % status_code if (status_code > -1) else '' - check = 'You may want to check the URL with your web browser.\n' - err_message = 'Connection fail to URL %s %s\n%s' % (location, s_str, check) + s_str = "with status: %s" % status_code if (status_code > -1) else "" + check = "You may want to check the URL with your web browser.\n" + err_message = "Connection fail to URL %s %s\n%s" % (location, s_str, check) if cls.firefly_lab_extension and cls.firefly_url_lab: - err_message += ('\nCheck the Firefly URL in ~/.jupyter/jupyter_notebook_config.py' + - ' or ~/.jupyter/jupyter_notebook_config.json') + err_message += ( + "\nCheck the Firefly URL in ~/.jupyter/jupyter_notebook_config.py" + + " or ~/.jupyter/jupyter_notebook_config.json" + ) elif cls.firefly_url: - err_message += 'Check setting of FIREFLY_URL environment variable: %s' % cls.firefly_url + err_message += ( + "Check setting of FIREFLY_URL environment variable: %s" + % cls.firefly_url + ) return err_message diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index 11336a2..193a66a 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -1,25 +1,48 @@ import json -import urllib.parse -import mimetypes import base64 +import mimetypes +import urllib.parse class DebugMarker: firefly_client_debug = False -def str_2_bool(v): return v.lower() in ("yes", "true", "t", "1") -def _make_key(channel, location): return channel+'---'+location -def debug(s): DebugMarker.firefly_client_debug and print('DEBUG: %s' % s) -def warn(s): print('WARNING: %s' % s) -def dict_to_str(in_dict): return json.dumps(in_dict, indent=2, default=str) +def str_2_bool(v): + return v.lower() in ("yes", "true", "t", "1") + + +def _make_key(channel, location): + return channel + "---" + location + + +def debug(s): + DebugMarker.firefly_client_debug and print("DEBUG: %s" % s) + + +def warn(s): + print("WARNING: %s" % s) + + +def dict_to_str(in_dict): + return json.dumps(in_dict, indent=2, default=str) # id for table, region layer, extension -_item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0, 'MaskLayer': 0, 'XYPlot': 0, - 'Cell': 0, 'Histogram': 0, 'Plotly': 0, 'Image': 0, 'FootprintLayer': 0} +_item_id = { + "Table": 0, + "RegionLayer": 0, + "Extension": 0, + "MaskLayer": 0, + "XYPlot": 0, + "Cell": 0, + "Histogram": 0, + "Plotly": 0, + "Image": 0, + "FootprintLayer": 0, +} -ALL = 'ALL_EVENTS_ENABLED' +ALL = "ALL_EVENTS_ENABLED" def gen_item_id(item): @@ -39,75 +62,84 @@ def gen_item_id(item): if item in _item_id: _item_id[item] += 1 - return item + '-' + str(_item_id[item]) + return item + "-" + str(_item_id[item]) else: return None def create_image_url(image_source): - def is_url(url): return urllib.parse.urlparse(url).scheme != '' + def is_url(url): + return urllib.parse.urlparse(url).scheme != "" - if not image_source.startswith('data:image') and not is_url(image_source): + if not image_source.startswith("data:image") and not is_url(image_source): mime, _ = mimetypes.guess_type(image_source) - with open(image_source, 'rb') as fp: + with open(image_source, "rb") as fp: data = fp.read() - data_uri = b''.join(base64.encodestring(data).splitlines()) - return 'data:%s;base64,%s' % (mime, data_uri) + data_uri = b"".join(base64.encodestring(data).splitlines()) + return "data:%s;base64,%s" % (mime, data_uri) return image_source def ensure3(val, name): - """ Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError """ - ret = val if type(val) == list else [val, val, val] + """Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError""" + ret = val if isinstance(val, list) else [val, val, val] if not len(ret) == 3: - raise ValueError('%s list should have 3 items' % name) + raise ValueError("%s list should have 3 items" % name) return ret # actions from Firefly ACTION_DICT = { - 'ShowFits': 'ImagePlotCntlr.PlotImage', - 'AddExtension': 'ExternalAccessCntlr/extensionAdd', - 'FetchTable': 'table.fetch', - 'ShowTable': 'table.search', - 'TableFilter': 'table.filter', - 'TableSort': 'table.sort', - 'ShowXYPlot': 'charts.data/chartAdd', - 'ShowPlot': 'charts.data/chartAdd', - 'ZoomImage': 'ImagePlotCntlr.ZoomImage', - 'PanImage': 'ImagePlotCntlr.recenter', - 'AlignImages': 'ImagePlotCntlr.wcsMatch', - 'StretchImage': 'ImagePlotCntlr.StretchChange', - 'ColorImage': 'ImagePlotCntlr.ColorChange', - 'CreateRegionLayer': 'DrawLayerCntlr.RegionPlot.createLayer', - 'DeleteRegionLayer': 'DrawLayerCntlr.RegionPlot.deleteLayer', - 'AddRegionData': 'DrawLayerCntlr.RegionPlot.addRegion', - 'RemoveRegionData': 'DrawLayerCntlr.RegionPlot.removeRegion', - 'PlotMask': 'ImagePlotCntlr.plotMask', - 'DeleteOverlayMask': 'ImagePlotCntlr.deleteOverlayPlot', - 'AddCell': 'layout.addCell', - 'SetLayoutMode': 'layout.setLayoutMode', - 'UpdateLayout': 'layout.updateLayout', - 'TriviewLayout': 'layout.triviewLayout', - 'ShowCoverage': 'layout.enableSpecialViewer', - 'ShowImageMetaData': 'layout.enableSpecialViewer', - 'ReinitViewer': 'app_data.reinitApp', - 'ShowHiPS': 'ImagePlotCntlr.PlotHiPS', - 'ShowImageOrHiPS': 'ImagePlotCntlr.plotHiPSOrImage', - 'ImagelineBasedFootprint': 'DrawLayerCntlr.ImageLineBasedFP.imagelineBasedFPCreate', - 'StartLabWindow': 'StartLabWindow', - 'StartBrowserTab': 'StartBrowserTab' + "ShowFits": "ImagePlotCntlr.PlotImage", + "AddExtension": "ExternalAccessCntlr/extensionAdd", + "FetchTable": "table.fetch", + "ShowTable": "table.search", + "TableFilter": "table.filter", + "TableSort": "table.sort", + "ShowXYPlot": "charts.data/chartAdd", + "ShowPlot": "charts.data/chartAdd", + "ZoomImage": "ImagePlotCntlr.ZoomImage", + "PanImage": "ImagePlotCntlr.recenter", + "AlignImages": "ImagePlotCntlr.wcsMatch", + "StretchImage": "ImagePlotCntlr.StretchChange", + "ColorImage": "ImagePlotCntlr.ColorChange", + "CreateRegionLayer": "DrawLayerCntlr.RegionPlot.createLayer", + "DeleteRegionLayer": "DrawLayerCntlr.RegionPlot.deleteLayer", + "AddRegionData": "DrawLayerCntlr.RegionPlot.addRegion", + "RemoveRegionData": "DrawLayerCntlr.RegionPlot.removeRegion", + "PlotMask": "ImagePlotCntlr.plotMask", + "DeleteOverlayMask": "ImagePlotCntlr.deleteOverlayPlot", + "AddCell": "layout.addCell", + "SetLayoutMode": "layout.setLayoutMode", + "UpdateLayout": "layout.updateLayout", + "TriviewLayout": "layout.triviewLayout", + "ShowCoverage": "layout.enableSpecialViewer", + "ShowImageMetaData": "layout.enableSpecialViewer", + "ReinitViewer": "app_data.reinitApp", + "ShowHiPS": "ImagePlotCntlr.PlotHiPS", + "ShowImageOrHiPS": "ImagePlotCntlr.plotHiPSOrImage", + "ImagelineBasedFootprint": "DrawLayerCntlr.ImageLineBasedFP.imagelineBasedFPCreate", + "StartLabWindow": "StartLabWindow", + "StartBrowserTab": "StartBrowserTab", } """Definition of Firefly action (`dict`).""" # layout view type -LO_VIEW_DICT = {'table': 'tables', - 'image': 'images', - 'xyPlot': 'xyPlots', - 'imageMeta': 'tableImageMeta', - 'coverImage': 'coverageImage'} +LO_VIEW_DICT = { + "table": "tables", + "image": "images", + "xyPlot": "xyPlots", + "imageMeta": "tableImageMeta", + "coverImage": "coverageImage", +} """Definition of layout viewer (`dict`).""" # extension type -EXTENSION_TYPE = ['AREA_SELECT', 'LINE_SELECT', 'POINT', 'table.highlight', 'table.select'] +EXTENSION_TYPE = [ + "AREA_SELECT", + "LINE_SELECT", + "POINT", + "table.highlight", + "table.select", +] """Type of plot where the extension is added to (`list` of `str`).""" diff --git a/firefly_client/ffws.py b/firefly_client/ffws.py index 0246792..e1596ef 100644 --- a/firefly_client/ffws.py +++ b/firefly_client/ffws.py @@ -1,25 +1,29 @@ -import os import json import time -from urllib.parse import urljoin -import math -import base64 -import traceback import _thread +import traceback from copy import deepcopy +from json import JSONDecodeError +from typing import ClassVar +from urllib.parse import urljoin + + try: from .env import Env except ImportError: from env import Env try: - from .fc_utils import ALL, debug, warn, dict_to_str, DebugMarker + from .fc_utils import ALL, DebugMarker, warn, debug, dict_to_str except ImportError: - from fc_utils import ALL, debug, warn, dict_to_str, DebugMarker + from fc_utils import ALL, DebugMarker, warn, debug, dict_to_str MAX_CHANNELS = 3 -def _make_key(channel, location): return channel+'---'+location + + +def _make_key(channel, location): + return channel + "---" + location class FFWs: @@ -28,23 +32,29 @@ class FFWs: directly. It should only be used though the static methods """ - connections = {} + connections: ClassVar = {} @classmethod - def has(cls, channel, location): return _make_key(channel, location) in cls.connections + def has(cls, channel, location): + return _make_key(channel, location) in cls.connections @classmethod - def get(cls, channel, location): return cls.connections.get(_make_key(channel, location)) + def get(cls, channel, location): + return cls.connections.get(_make_key(channel, location)) @classmethod def _open_ws_connection(cls, channel, wsproto, location, auth_headers, header_cb): key = _make_key(channel, location) if key not in cls.connections: if len(cls.connections) > MAX_CHANNELS: - err_msg = 'You may only use %s channels for a python session' % MAX_CHANNELS + err_msg = ( + "You may only use %s channels for a python session" % MAX_CHANNELS + ) raise ConnectionRefusedError(err_msg) - cls.connections[key] = cls(channel, wsproto, location, auth_headers, header_cb) - debug('starting chan: %s %s url:%s' % (channel, wsproto, location)) + cls.connections[key] = cls( + channel, wsproto, location, auth_headers, header_cb + ) + debug("starting chan: %s %s url:%s" % (channel, wsproto, location)) return cls.connections[key] @classmethod @@ -54,9 +64,20 @@ def close_ws_connection(cls, channel, location): cls.connections.pop(_make_key(channel, location), None) @classmethod - def add_listener(cls, wsproto, auth_headers, channel, location, callback, name=ALL, header_cb=None): + def add_listener( + cls, + wsproto, + auth_headers, + channel, + location, + callback, + name=ALL, + header_cb=None, + ): cls._open_ws_connection(channel, wsproto, location, auth_headers, header_cb) - cls.has(channel, location) and cls.get(channel, location).do_add_listener(callback, name) + cls.has(channel, location) and cls.get(channel, location).do_add_listener( + callback, name + ) @classmethod def remove_listener(cls, channel, location, callback, name=ALL): @@ -72,11 +93,13 @@ def wait_for_events(cls, channel, location): cls.has(channel, location) and cls.get(channel, location).do_run_forever() def __init__(self, channel, wsproto, location, auth_headers, header_cb): - - self.ws_url = urljoin('{}://{}/'.format(wsproto, location), 'sticky/firefly/events?channelID=%s' % channel) + self.ws_url = urljoin( + "{}://{}/".format(wsproto, location), + "sticky/firefly/events?channelID=%s" % channel, + ) self.channel = channel self.location = location - self.channel_headers = {'FF-channel': channel} + self.channel_headers = {"FF-channel": channel} self.listeners = {} self.forever_loop = True @@ -91,33 +114,45 @@ def on_open(wsapp): if not DebugMarker.firefly_client_debug: return try: - debug('on open: Status: %d' % wsapp.sock.handshake_response.status) - debug('response headers: \n%s' % dict_to_str(wsapp.sock.handshake_response.headers)) + debug("on open: Status: %d" % wsapp.sock.handshake_response.status) + debug( + "response headers: \n%s" + % dict_to_str(wsapp.sock.handshake_response.headers) + ) except Exception as open_ex: print(traceback.format_exc()) raise open_ex def on_error(wsapp, exception_from_socket): - warn('Error: Websocket connection failed') + warn("Error: Websocket connection failed") print(exception_from_socket) - warn('Websocket Status: %d' % wsapp.sock.handshake_response.status) - warn('Websocket response headers: \n%s' % dict_to_str(wsapp.sock.handshake_response.headers)) + warn("Websocket Status: %d" % wsapp.sock.handshake_response.status) + warn( + "Websocket response headers: \n%s" + % dict_to_str(wsapp.sock.handshake_response.headers) + ) raise exception_from_socket def threaded_connect(): try: import websocket + socket_headers = self.channel_headers.copy() if auth_headers is not None: socket_headers.update(auth_headers) - self.websocket = websocket.WebSocketApp(url=self.ws_url, header=socket_headers, on_message=on_message, - on_open=on_open, on_error=on_error) + self.websocket = websocket.WebSocketApp( + url=self.ws_url, + header=socket_headers, + on_message=on_message, + on_open=on_open, + on_error=on_error, + ) self.debug_show_env(socket_headers) self.websocket.run_forever(ping_interval=10) self.forever_loop = False - debug('websocket thread ended') + debug("websocket thread ended") except Exception: - debug('websocket thread ended with exception') + debug("websocket thread ended with exception") print(traceback.format_exc()) try: @@ -128,46 +163,55 @@ def threaded_connect(): def debug_show_env(self, socket_headers): if not DebugMarker.firefly_client_debug: return - debug('Attempting to connect\n %s\n %s\n channel: %s' % (self.location, self.ws_url, self.channel)) - debug('Header sent to websocket connections: %s' % dict_to_str(socket_headers)) + debug( + "Attempting to connect\n %s\n %s\n channel: %s" + % (self.location, self.ws_url, self.channel) + ) + debug("Header sent to websocket connections: %s" % dict_to_str(socket_headers)) def debug_header_event_message(self, ev): if not DebugMarker.firefly_client_debug: return - debug('Event: %s' % ev['name']) + debug("Event: %s" % ev["name"]) log_ev = deepcopy(ev) try: - log_ev['data']['plotState']['bandStateAry'] = '<<<<>>>>' + log_ev["data"]["plotState"]["bandStateAry"] = "<<<<>>>>" except KeyError: pass - debug('JSON Data:\n%s' % json.dumps(log_ev, indent=2, default=str)) - debug('All Listeners for channel: %s, location: %s' % (self.channel, self.location)) + debug("JSON Data:\n%s" % json.dumps(log_ev, indent=2, default=str)) + debug( + "All Listeners for channel: %s, location: %s" + % (self.channel, self.location) + ) for callback, eventIDList in self.listeners.items(): debug(" %s" % eventIDList) self.execute_callbacks(ev, do_callback=False) def execute_callbacks(self, ev, do_callback=True): - name = ev['name'] + name = ev["name"] for callback, eventIDList in self.listeners.items(): if name in eventIDList or ALL in eventIDList: - callback(ev) if do_callback else debug('callback: %s' % name) + callback(ev) if do_callback else debug("callback: %s" % name) def received_message(self, message, header_cb): try: ev = json.loads(message) except JSONDecodeError as err: - warn('Error with JSON input - event string could not be parsed') + warn("Error with JSON input - event string could not be parsed") warn(message) warn(err) return - if ev['name'] == 'EVT_CONN_EST': + if ev["name"] == "EVT_CONN_EST": try: - conn_info = ev['data'] - debug('Connection established:\n %s' % message) + conn_info = ev["data"] + debug("Connection established:\n %s" % message) if self.channel is None: - self.channel = conn_info['channel'] - self.channel_headers = {'FF-channel': self.channel, 'FF-connID': conn_info.get('connID')} + self.channel = conn_info["channel"] + self.channel_headers = { + "FF-channel": self.channel, + "FF-connID": conn_info.get("connID"), + } header_cb(self.channel_headers) except Exception as err: print(message) @@ -177,19 +221,18 @@ def received_message(self, message, header_cb): self.execute_callbacks(ev) def disconnect(self): - """Disconnect the WebSocket. - """ + """Disconnect the WebSocket.""" self.websocket.close() def do_add_listener(self, callback, name=ALL): - debug('adding listener to %s, %s' % (self.channel, self.ws_url)) + debug("adding listener to %s, %s" % (self.channel, self.ws_url)) if callback not in self.listeners.keys(): self.listeners[callback] = [] if name not in self.listeners[callback]: self.listeners[callback].append(name) def do_remove_listener(self, callback, name=ALL): - debug('removing listener to %s, %s' % (self.channel, self.ws_url)) + debug("removing listener to %s, %s" % (self.channel, self.ws_url)) if callback in self.listeners.keys(): if name in self.listeners[callback]: self.listeners[callback].remove(name) diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index 3658723..5a47902 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -4,15 +4,18 @@ This module defines class 'FireflyClient' and methods to remotely communicate to Firefly viewer by dispatching remote actions. """ -import requests -import webbrowser + import json +import math import time import socket -from urllib.parse import urljoin -import math import weakref +import webbrowser from copy import copy +from typing import ClassVar +from urllib.parse import urljoin + +import requests try: @@ -28,19 +31,39 @@ except ImportError: from range_values import RangeValues try: - from .fc_utils import debug, warn, dict_to_str, create_image_url, ensure3, gen_item_id,\ - DebugMarker, ALL, ACTION_DICT, LO_VIEW_DICT + from .fc_utils import ( + ALL, + ACTION_DICT, + LO_VIEW_DICT, + DebugMarker, + warn, + debug, + ensure3, + dict_to_str, + gen_item_id, + create_image_url, + ) except ImportError: - from fc_utils import debug, warn, dict_to_str, create_image_url, ensure3, gen_item_id,\ - DebugMarker, ALL, ACTION_DICT, LO_VIEW_DICT - -__docformat__ = 'restructuredtext' + from fc_utils import ( + ALL, + ACTION_DICT, + LO_VIEW_DICT, + DebugMarker, + warn, + debug, + ensure3, + dict_to_str, + gen_item_id, + create_image_url, + ) + +__docformat__ = "restructuredtext" _def_html_file = Env.find_default_firefly_html() _default_url = Env.find_default_firefly_url() -BROWSER = 'browser' -LAB = 'lab' -UNKNOWN = 'UNKNOWN' +BROWSER = "browser" +LAB = "lab" +UNKNOWN = "UNKNOWN" class FireflyClient: @@ -74,54 +97,70 @@ class FireflyClient: in the sessions attribute. """ - TAB_ID = 'firefly-viewer-tab-id' - TRIVIEW_ICov_Ch_T = 'TRIVIEW_ICov_Ch_T' - TRIVIEW_I_ChCov_T = 'TRIVIEW_I_ChCov_T' - BIVIEW_ICov_Ch = 'BIVIEW_ICov_Ch' - BIVIEW_I_ChCov = 'BIVIEW_I_ChCov' - BIVIEW_T_IChCov = 'BIVIEW_T_IChCov' - BIVIEW_IChCov_T = 'BIVIEW_IChCov_T' - tri_view_types_list = [ + TAB_ID = "firefly-viewer-tab-id" + TRIVIEW_ICov_Ch_T = "TRIVIEW_ICov_Ch_T" + TRIVIEW_I_ChCov_T = "TRIVIEW_I_ChCov_T" + BIVIEW_ICov_Ch = "BIVIEW_ICov_Ch" + BIVIEW_I_ChCov = "BIVIEW_I_ChCov" + BIVIEW_T_IChCov = "BIVIEW_T_IChCov" + BIVIEW_IChCov_T = "BIVIEW_IChCov_T" + tri_view_types_list: ClassVar = [ TRIVIEW_ICov_Ch_T, TRIVIEW_I_ChCov_T, BIVIEW_ICov_Ch, BIVIEW_I_ChCov, BIVIEW_T_IChCov, - BIVIEW_IChCov_T + BIVIEW_IChCov_T, ] - tri_view_layout_desc = { - TRIVIEW_ICov_Ch_T: 'top left: image/cov, top right: charts, bottom: tables', - TRIVIEW_I_ChCov_T: 'top left: image, top right: charts/cov, bottom: tables', - BIVIEW_ICov_Ch: 'left: image/cov, right: charts', - BIVIEW_I_ChCov: 'left: image, right: charts/cov', - BIVIEW_T_IChCov: 'left: tables, right: image/charts/cov', - BIVIEW_IChCov_T: 'left: image/charts/cov, right: tables', + tri_view_layout_desc: ClassVar = { + TRIVIEW_ICov_Ch_T: "top left: image/cov, top right: charts, bottom: tables", + TRIVIEW_I_ChCov_T: "top left: image, top right: charts/cov, bottom: tables", + BIVIEW_ICov_Ch: "left: image/cov, right: charts", + BIVIEW_I_ChCov: "left: image, right: charts/cov", + BIVIEW_T_IChCov: "left: tables, right: image/charts/cov", + BIVIEW_IChCov_T: "left: image/charts/cov, right: tables", } # viewer modes - TRIVIEW_VIEWER = 'FireflyViewer' - SLATE_VIEWER = 'FireflySlate' - NO_VIEWER = 'NO_VIEWER' - _viewer_modes = [TRIVIEW_VIEWER,SLATE_VIEWER,NO_VIEWER] + TRIVIEW_VIEWER = "FireflyViewer" + SLATE_VIEWER = "FireflySlate" + NO_VIEWER = "NO_VIEWER" + _viewer_modes: ClassVar = [TRIVIEW_VIEWER, SLATE_VIEWER, NO_VIEWER] # viewer ids - PINNED_CHART_VIEWER_ID = 'PINNED_CHART_VIEWER_ID' - PINNED_IMAGE_VIEWER_ID = 'DEFAULT_FITS_VIEWER_ID' + PINNED_CHART_VIEWER_ID = "PINNED_CHART_VIEWER_ID" + PINNED_IMAGE_VIEWER_ID = "DEFAULT_FITS_VIEWER_ID" _debug = False # Keep track of instances. - instances = [] + instances: ClassVar = [] """All events are enabled for the listener (`str`).""" # id for table, region layer, extension - _item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0, 'MaskLayer': 0, 'XYPlot': 0, - 'Cell': 0, 'Histogram': 0, 'Plotly': 0, 'Image': 0, 'FootprintLayer': 0} + _item_id: ClassVar = { + "Table": 0, + "RegionLayer": 0, + "Extension": 0, + "MaskLayer": 0, + "XYPlot": 0, + "Cell": 0, + "Histogram": 0, + "Plotly": 0, + "Image": 0, + "FootprintLayer": 0, + } @classmethod - def make_lab_client(cls, start_browser_tab=False, html_file=_def_html_file, start_tab=True, - verbose=False, token=None): + def make_lab_client( + cls, + start_browser_tab=False, + html_file=_def_html_file, + start_tab=True, + verbose=False, + token=None, + ): """ Factory method to create a Firefly client in the Jupyterlab environment. If you are using Jupyterlab with the jupyter_firefly_extension installed, @@ -157,17 +196,31 @@ def make_lab_client(cls, start_browser_tab=False, html_file=_def_html_file, star out : `FireflyClient` A FireflyClient that works in the lab environment """ - tab_type = BROWSER if (start_browser_tab and start_tab) else (LAB if start_tab else None) + tab_type = ( + BROWSER + if (start_browser_tab and start_tab) + else (LAB if start_tab else None) + ) url, channel = Env.validate_lab_client(tab_type == BROWSER) fc = cls(url, channel, html_file, token) if tab_type: - verbose and tab_type == BROWSER and Env.show_start_browser_tab_msg(fc.get_firefly_url()) + verbose and tab_type == BROWSER and Env.show_start_browser_tab_msg( + fc.get_firefly_url() + ) fc._lab_env_tab_start(tab_type, html_file) return fc @classmethod - def make_client(cls, url=_default_url, html_file=_def_html_file, launch_browser=True, - channel_override=None, verbose=False, token=None, viewer_override=None): + def make_client( + cls, + url=_default_url, + html_file=_def_html_file, + launch_browser=True, + channel_override=None, + verbose=False, + token=None, + viewer_override=None, + ): """ Factory method to create a Firefly client in a plain Python, IPython, or notebook session, and attempt to open a display. If a display cannot be @@ -213,55 +266,80 @@ def make_client(cls, url=_default_url, html_file=_def_html_file, launch_browser= fc : `FireflyClient` A FireflyClient that works in the lab environment """ - fc = cls(url, Env.resolve_client_channel(channel_override), html_file, token, viewer_override) + fc = cls( + url, + Env.resolve_client_channel(channel_override), + html_file, + token, + viewer_override, + ) verbose and Env.show_start_browser_tab_msg(fc.get_firefly_url()) launch_browser and fc.launch_browser() return fc - def __init__(self, url, channel, html_file=_def_html_file, token=None, viewer_override=None): + def __init__( + self, url, channel, html_file=_def_html_file, token=None, viewer_override=None + ): DebugMarker.firefly_client_debug = FireflyClient._debug FireflyClient.instances.append(weakref.ref(self)) - ssl = url.startswith('https://') - self.wsproto = 'wss' if ssl else 'ws' + ssl = url.startswith("https://") + self.wsproto = "wss" if ssl else "ws" self.location = url[8:] if ssl else url[7:] - self.location = self.location[:-1] if self.location.endswith('/') else self.location + self.location = ( + self.location[:-1] if self.location.endswith("/") else self.location + ) self.url = url self.channel = channel self.render_tree_id = None - self.auth_headers = {'Authorization': 'Bearer {}'.format(token)} if token and ssl else None - self.header_from_ws = {'FF-channel': channel} + self.auth_headers = ( + {"Authorization": "Bearer {}".format(token)} if token and ssl else None + ) + self.header_from_ws = {"FF-channel": channel} self.lab_env_tab_type = UNKNOWN # urls for cmd service and browser - protocol = 'https' if ssl else 'http' - self.url_cmd_service = urljoin('{}://{}/'.format(protocol, self.location), 'sticky/CmdSrv') - self.url_browser = urljoin(urljoin('{}://{}/'.format(protocol, self.location), html_file), '?__wsch=') + protocol = "https" if ssl else "http" + self.url_cmd_service = urljoin( + "{}://{}/".format(protocol, self.location), "sticky/CmdSrv" + ) + self.url_browser = urljoin( + urljoin("{}://{}/".format(protocol, self.location), html_file), "?__wsch=" + ) self.url_bw = self.url_browser # keep around for backward compatibility self.session = requests.Session() token and ssl and self.session.headers.update(self.auth_headers) - not ssl and token and warn('token ignored: should be None when url starts with http://') - self.firefly_viewer = FireflyClient.get_viewer_mode(html_file,viewer_override) - debug('new instance: %s' % url) + not ssl and token and warn( + "token ignored: should be None when url starts with http://" + ) + self.firefly_viewer = FireflyClient.get_viewer_mode(html_file, viewer_override) + debug("new instance: %s" % url) def _lab_env_tab_start(self, tab_type, html_file): - """start a tab in the lab environment, tab_type must be 'lab' or 'browser' """ + """start a tab in the lab environment, tab_type must be 'lab' or 'browser'""" self.lab_env_tab_type = tab_type if tab_type == BROWSER: - idx = self.channel.find('__viewer') - c = self.channel[0:idx] if idx > -1 else self.channel # the ext will add '__viewer' so I have to remove it - self.dispatch(ACTION_DICT['StartBrowserTab'], - {'channel': c, 'fireflyHtmlFile': _def_html_file}, Env.firefly_channel_lab) + idx = self.channel.find("__viewer") + c = ( + self.channel[0:idx] if idx > -1 else self.channel + ) # the ext will add '__viewer' so I have to remove it + self.dispatch( + ACTION_DICT["StartBrowserTab"], + {"channel": c, "fireflyHtmlFile": _def_html_file}, + Env.firefly_channel_lab, + ) elif tab_type == LAB: if not self.render_tree_id: - self.render_tree_id = FireflyClient.TAB_ID # no longer generating redner_tree_id + self.render_tree_id = ( + FireflyClient.TAB_ID + ) # no longer generating redner_tree_id # self.render_tree_id = 'slateClient-%s-%s' % (len(self.instances), round(time.time())) self.show_lab_tab(html_file) def show_lab_tab(self, html_file=_def_html_file): """If using a jupyter lab tab - show it or reopen it. If not using a lab tab then noop""" - self.lab_env_tab_type = (LAB and - self.dispatch(ACTION_DICT['StartLabWindow'], - {'fireflyHtmlFile': html_file})) + self.lab_env_tab_type = LAB and self.dispatch( + ACTION_DICT["StartLabWindow"], {"fireflyHtmlFile": html_file} + ) @staticmethod def get_viewer_mode(html_file, viewer_override): @@ -269,16 +347,28 @@ def get_viewer_mode(html_file, viewer_override): if viewer_override in FireflyClient._viewer_modes: return viewer_override else: - warn('viewer_override mode: {} is not a recognized mode, using {}'.format(viewer_override, UNKNOWN)) + warn( + "viewer_override mode: {} is not a recognized mode, using {}".format( + viewer_override, UNKNOWN + ) + ) return UNKNOWN else: - return FireflyClient.SLATE_VIEWER if html_file == 'slate.html' else FireflyClient.TRIVIEW_VIEWER + return ( + FireflyClient.SLATE_VIEWER + if html_file == "slate.html" + else FireflyClient.TRIVIEW_VIEWER + ) def _send_url_as_get(self, url): return self.call_response(self.session.get(url, headers=self.header_from_ws)) def _send_url_as_post(self, data): - return self.call_response(self.session.post(self.url_cmd_service, data=data, headers=self.header_from_ws)) + return self.call_response( + self.session.post( + self.url_cmd_service, data=data, headers=self.header_from_ws + ) + ) def call_response(self, response): if response.status_code != 200: @@ -287,47 +377,49 @@ def call_response(self, response): status = json.loads(response.text) return status[0] except ValueError as err: - warn('JSON parsing Error:') + warn("JSON parsing Error:") if len(response.text) > 300: - warn('Response string (first 300 characters):\n' + response.text[0:300]) - debug('Full Response:\n' + response.text) + warn("Response string (first 300 characters):\n" + response.text[0:300]) + debug("Full Response:\n" + response.text) else: - warn('Response string:\n' + response.text[0:300]) + warn("Response string:\n" + response.text[0:300]) raise err - + @staticmethod def _get_ip(): """Find local IP address, based on https://stackoverflow.com/q/166506/8252556.""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: - s.connect(('8.8.8.8', 1)) # doesn't even have to be reachable + s.connect(("8.8.8.8", 1)) # doesn't even have to be reachable ip = s.getsockname()[0] except Exception: - ip = '127.0.0.1' + ip = "127.0.0.1" finally: s.close() return ip def _is_page_connected(self): """Check if the page is connected.""" - url = f'{self.url_cmd_service}?cmd=pushAliveCheck&ipAddress={self._get_ip()}' + url = f"{self.url_cmd_service}?cmd=pushAliveCheck&ipAddress={self._get_ip()}" retval = self._send_url_as_get(url) - return retval['active'] + return retval["active"] - def is_triview(self): return self.firefly_viewer == FireflyClient.TRIVIEW_VIEWER + def is_triview(self): + return self.firefly_viewer == FireflyClient.TRIVIEW_VIEWER - def is_slate(self): return self.firefly_viewer == FireflyClient.SLATE_VIEWER + def is_slate(self): + return self.firefly_viewer == FireflyClient.SLATE_VIEWER @staticmethod def _make_pid_param(plot_id): - return ','.join(plot_id) if isinstance(plot_id, list) else plot_id + return ",".join(plot_id) if isinstance(plot_id, list) else plot_id -# ----------------------------------------------------------------- -# ----------------------------------------------------------------- -# Public API Begins -# ----------------------------------------------------------------- -# ----------------------------------------------------------------- + # ----------------------------------------------------------------- + # ----------------------------------------------------------------- + # Public API Begins + # ----------------------------------------------------------------- + # ----------------------------------------------------------------- def add_listener(self, callback, name=ALL): """ Add a callback function to listen for events on the Firefly client. @@ -345,8 +437,19 @@ def add_listener(self, callback, name=ALL): """ try: - def header_cb(headers): self.header_from_ws = headers - FFWs.add_listener(self.wsproto, self.auth_headers, self.channel, self.location, callback, name, header_cb) + + def header_cb(headers): + self.header_from_ws = headers + + FFWs.add_listener( + self.wsproto, + self.auth_headers, + self.channel, + self.location, + callback, + name, + header_cb, + ) except ConnectionRefusedError as err: raise ValueError(f"Couldn't add listener: {err}") from err @@ -382,8 +485,7 @@ def wait_for_events(self): FFWs.wait_for_events(self.channel, self.location) def disconnect(self): - """DEPRECATED. Now just remove the listeners. Disconnect the WebSocket. - """ + """DEPRECATED. Now just remove the listeners. Disconnect the WebSocket.""" FFWs.close_ws_connection(self.channel, self.location) def get_firefly_url(self, channel=None): @@ -417,12 +519,19 @@ def display_url(self, url=None): try: ipy_str = str(type(get_ipython())) except NameError: - ipy_str = '' - if 'zmqshell' in ipy_str: - from IPython.display import display, HTML - display(HTML('Open your web browser to this link'.format(url))) + ipy_str = "" + if "zmqshell" in ipy_str: + from IPython.display import HTML, display + + display( + HTML( + 'Open your web browser to this link'.format( + url + ) + ) + ) else: - print('Open your web browser to {}'.format(url)) + print("Open your web browser to {}".format(url)) def launch_browser(self, channel=None, force=False, verbose=True): """ @@ -493,13 +602,13 @@ def upload_file(self, path): .. note:: 'pre_load' is not implemented in the server (will be removed later). """ - url = self.url_cmd_service + '?cmd=upload' - files = {'file': open(path, 'rb')} + url = self.url_cmd_service + "?cmd=upload" + files = {"file": open(path, "rb")} result = self.session.post(url, files=files, headers=self.header_from_ws) if result.status_code == 200: - index = result.text.find('$') + index = result.text.find("$") return result.text[index:] - raise requests.HTTPError('Upload unsuccessful') + raise requests.HTTPError("Upload unsuccessful") def upload_fits_data(self, stream): """ @@ -517,7 +626,7 @@ def upload_fits_data(self, stream): out : `dict` Status, like {'success': True}. """ - return self.upload_data(stream, 'FITS') + return self.upload_data(stream, "FITS") def upload_text_data(self, stream): """ @@ -535,7 +644,7 @@ def upload_text_data(self, stream): out : `dict` Status, like {'success': True}. """ - return self.upload_data(stream, 'UNKNOWN') + return self.upload_data(stream, "UNKNOWN") def upload_data(self, stream, data_type): """ @@ -555,15 +664,15 @@ def upload_data(self, stream, data_type): Status, like {'success': True}. """ - url = self.url_cmd_service + '?cmd=upload&preload=' - url += 'true&type=FITS' if data_type.upper() == 'FITS' else 'false&type=UNKNOWN' + url = self.url_cmd_service + "?cmd=upload&preload=" + url += "true&type=FITS" if data_type.upper() == "FITS" else "false&type=UNKNOWN" stream.seek(0, 0) - data_pack = {'data': stream} + data_pack = {"data": stream} result = self.session.post(url, files=data_pack, headers=self.header_from_ws) if result.status_code == 200: - index = result.text.find('$') + index = result.text.find("$") return result.text[index:] - raise requests.HTTPError('Upload unsuccessful') + raise requests.HTTPError("Upload unsuccessful") @staticmethod def create_image_url(image_source): @@ -604,11 +713,14 @@ def dispatch(self, action_type, payload, override_channel=None): if payload is None: payload = {} if self.render_tree_id: - payload['renderTreeId'] = self.render_tree_id + payload["renderTreeId"] = self.render_tree_id channel = self.channel if override_channel is None else override_channel - action = {'type': action_type, 'payload': payload} - data = {'channelID': channel, 'cmd': 'pushAction', 'action': json.dumps(action)} - debug('dispatch: type: %s, channel: %s \n%s' % (action_type, channel, dict_to_str(action))) + action = {"type": action_type, "payload": payload} + data = {"channelID": channel, "cmd": "pushAction", "action": json.dumps(action)} + debug( + "dispatch: type: %s, channel: %s \n%s" + % (action_type, channel, dict_to_str(action)) + ) return self._send_url_as_post(data) @@ -638,13 +750,21 @@ def change_triview_layout(self, layout): """ if not self.is_triview(): - return {'success': True, 'warning': 'change_triview_layout ignored when not in triview mode'} + return { + "success": True, + "warning": "change_triview_layout ignored when not in triview mode", + } if layout not in FireflyClient.tri_view_types_list: - warning = '{} is an unknown layout type, valid types: {}'.format(layout, FireflyClient.tri_view_types_list) - return {'success': False, 'warning': warning} + warning = "{} is an unknown layout type, valid types: {}".format( + layout, FireflyClient.tri_view_types_list + ) + return {"success": False, "warning": warning} - self.dispatch(ACTION_DICT['TriviewLayout'], {'triviewLayout': layout}) - return {'success': True, 'description': FireflyClient.tri_view_layout_desc[layout]} + self.dispatch(ACTION_DICT["TriviewLayout"], {"triviewLayout": layout}) + return { + "success": True, + "description": FireflyClient.tri_view_layout_desc[layout], + } def add_cell(self, row, col, width, height, element_type, cell_id=None): """ @@ -671,26 +791,31 @@ def add_cell(self, row, col, width, height, element_type, cell_id=None): Status of the request, like {'success': True, 'cell_id': 'Cell-1'}. """ if not self.is_slate(): - return {'success': True, 'cell_id': cell_id if cell_id else 'noop', - 'warning': 'add_cell ignored when not in slate mode'} + return { + "success": True, + "cell_id": cell_id if cell_id else "noop", + "warning": "add_cell ignored when not in slate mode", + } # force the cell_id to be 'main' for table's case - if element_type == LO_VIEW_DICT['table']: - if not cell_id or cell_id != 'main': - cell_id = 'main' + if element_type == LO_VIEW_DICT["table"]: + if not cell_id or cell_id != "main": + cell_id = "main" else: if not cell_id: - cell_id = gen_item_id('Cell') - - payload = {'row': row, - 'col': col, - 'width': width, - 'height': height, - 'type': element_type, - 'cellId': cell_id} - - r = self.dispatch(ACTION_DICT['AddCell'], payload) - r.update({'cell_id': cell_id}) + cell_id = gen_item_id("Cell") + + payload = { + "row": row, + "col": col, + "width": width, + "height": height, + "type": element_type, + "cellId": cell_id, + } + + r = self.dispatch(ACTION_DICT["AddCell"], payload) + r.update({"cell_id": cell_id}) return r def reinit_viewer(self): @@ -702,9 +827,11 @@ def reinit_viewer(self): out : `dict` Status of the request, like {'success': True}. """ - return self.dispatch(ACTION_DICT['ReinitViewer'], {}) + return self.dispatch(ACTION_DICT["ReinitViewer"], {}) - def show_fits(self, file_on_server=None, plot_id=None, viewer_id=None, **additional_params): + def show_fits( + self, file_on_server=None, plot_id=None, viewer_id=None, **additional_params + ): """ Show a FITS image. @@ -744,23 +871,25 @@ def show_fits(self, file_on_server=None, plot_id=None, viewer_id=None, **additio is used for image search. """ - wp_request = {'plotGroupId': 'groupFromPython', - 'GroupLocked': False} - payload = {'wpRequest': wp_request, - 'useContextModifications': True} + wp_request = {"plotGroupId": "groupFromPython", "GroupLocked": False} + payload = {"wpRequest": wp_request, "useContextModifications": True} warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) - plot_id and payload['wpRequest'].update({'plotId': plot_id}) - file_on_server and payload['wpRequest'].update({'file': file_on_server}) - additional_params and payload['wpRequest'].update(additional_params) + payload.update({"viewerId": viewer_id}) + plot_id and payload["wpRequest"].update({"plotId": plot_id}) + file_on_server and payload["wpRequest"].update({"file": file_on_server}) + additional_params and payload["wpRequest"].update(additional_params) - r = self.dispatch(ACTION_DICT['ShowFits'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowFits"], payload) + warning and r.update({"warning": warning}) return r def show_fits_3color(self, three_color_params, plot_id=None, viewer_id=None): @@ -786,28 +915,67 @@ def show_fits_3color(self, three_color_params, plot_id=None, viewer_id=None): Status of the request, like {'success': True}. """ - three_color = three_color_params if type(three_color_params).__name__ == 'list' else [three_color_params] + three_color = ( + three_color_params + if type(three_color_params).__name__ == "list" + else [three_color_params] + ) for item in three_color: - item.update({'GroupLocked': item.get('GroupLocked') if 'GroupLocked' in item else False}) - item.update({'plotGroupId': item.get('plotGroupId') if 'plotGroupId' in item else 'groupFromPython'}) - item.update({'Title': item.get('Title') if 'Title' in item else '3 Color'}) - if 'plotId' not in item and plot_id: - item.update({'plotId': plot_id}) - - payload = {'wpRequest': three_color, 'threeColor': True, 'useContextModifications': True} + item.update( + { + "GroupLocked": ( + item.get("GroupLocked") if "GroupLocked" in item else False + ) + } + ) + item.update( + { + "plotGroupId": ( + item.get("plotGroupId") + if "plotGroupId" in item + else "groupFromPython" + ) + } + ) + item.update({"Title": item.get("Title") if "Title" in item else "3 Color"}) + if "plotId" not in item and plot_id: + item.update({"plotId": plot_id}) + + payload = { + "wpRequest": three_color, + "threeColor": True, + "useContextModifications": True, + } warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) + payload.update({"viewerId": viewer_id}) - r = self.dispatch(ACTION_DICT['ShowFits'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowFits"], payload) + warning and r.update({"warning": warning}) return r - def show_table(self, file_on_server=None, url=None, tbl_id=None, title=None, page_size=100, is_catalog=True, - meta=None, target_search_info=None, options=None, table_index=None, - column_spec=None, filters=None, visible=True): + def show_table( + self, + file_on_server=None, + url=None, + tbl_id=None, + title=None, + page_size=100, + is_catalog=True, + meta=None, + target_search_info=None, + options=None, + table_index=None, + column_spec=None, + filters=None, + visible=True, + ): """ Show a table. @@ -890,38 +1058,63 @@ def show_table(self, file_on_server=None, url=None, tbl_id=None, title=None, pag """ if not tbl_id: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") if not title: - title = tbl_id if file_on_server or url else target_search_info.get('catalog', tbl_id) + title = ( + tbl_id + if file_on_server or url + else target_search_info.get("catalog", tbl_id) + ) - meta_info = {'title': title, 'tbl_id': tbl_id} + meta_info = {"title": title, "tbl_id": tbl_id} meta and meta_info.update(meta) - tbl_req = {'startIdx': 0, 'pageSize': page_size, 'tbl_id': tbl_id} + tbl_req = {"startIdx": 0, "pageSize": page_size, "tbl_id": tbl_id} if file_on_server or url: - tbl_type = 'table' if not is_catalog else 'catalog' + tbl_type = "table" if not is_catalog else "catalog" source = url if url else file_on_server - tbl_req.update({'source': source, 'tblType': tbl_type, - 'id': 'IpacTableFromSource'}) - table_index and tbl_req.update({'tbl_index': table_index}) + tbl_req.update( + {"source": source, "tblType": tbl_type, "id": "IpacTableFromSource"} + ) + table_index and tbl_req.update({"tbl_index": table_index}) elif target_search_info: target_search_info.update( - {'use': target_search_info.get('use') if 'use' in target_search_info else 'catalog_overlay'}) - tbl_req.update({'id': 'GatorQuery', 'UserTargetWorldPt': target_search_info.get('position')}) - target_search_info.pop('position', None) + { + "use": ( + target_search_info.get("use") + if "use" in target_search_info + else "catalog_overlay" + ) + } + ) + tbl_req.update( + { + "id": "GatorQuery", + "UserTargetWorldPt": target_search_info.get("position"), + } + ) + target_search_info.pop("position", None) tbl_req.update(target_search_info) - tbl_req.update({'META_INFO': meta_info}) - options and tbl_req.update({'options': options}) - column_spec and tbl_req.update({'inclCols': column_spec}) - filters and tbl_req.update({'filters': filters}) + tbl_req.update({"META_INFO": meta_info}) + options and tbl_req.update({"options": options}) + column_spec and tbl_req.update({"inclCols": column_spec}) + filters and tbl_req.update({"filters": filters}) - payload = {'request': tbl_req} - action_type = ACTION_DICT['ShowTable'] if visible else ACTION_DICT['FetchTable'] + payload = {"request": tbl_req} + action_type = ACTION_DICT["ShowTable"] if visible else ACTION_DICT["FetchTable"] return self.dispatch(action_type, payload) - def fetch_table(self, file_on_server, tbl_id=None, title=None, page_size=1, table_index=None, meta=None): + def fetch_table( + self, + file_on_server, + tbl_id=None, + title=None, + page_size=1, + table_index=None, + meta=None, + ): """ Fetch table data without showing them @@ -950,19 +1143,24 @@ def fetch_table(self, file_on_server, tbl_id=None, title=None, page_size=1, tabl Status of the request, like {'success': True}. """ if not tbl_id: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") if not title: title = tbl_id - tbl_req = {'startIdx': 0, 'pageSize': page_size, 'source': file_on_server, - 'id': 'IpacTableFromSource', 'tbl_id': tbl_id} + tbl_req = { + "startIdx": 0, + "pageSize": page_size, + "source": file_on_server, + "id": "IpacTableFromSource", + "tbl_id": tbl_id, + } if table_index: - tbl_req.update({'tbl_index': table_index}) + tbl_req.update({"tbl_index": table_index}) - meta_info = {'title': title, 'tbl_id': tbl_id} + meta_info = {"title": title, "tbl_id": tbl_id} meta and meta_info.update(meta) - tbl_req.update({'META_INFO': meta_info}) - payload = {'request': tbl_req, 'hlRowIdx': 0} - return self.dispatch(ACTION_DICT['FetchTable'], payload) + tbl_req.update({"META_INFO": meta_info}) + payload = {"request": tbl_req, "hlRowIdx": 0} + return self.dispatch(ACTION_DICT["FetchTable"], payload) def show_xyplot(self, tbl_id, standalone=False, group_id=None, **chart_params): """ @@ -1021,24 +1219,32 @@ def show_xyplot(self, tbl_id, standalone=False, group_id=None, **chart_params): parameters are valid. """ - cid = gen_item_id('XYPlot') + cid = gen_item_id("XYPlot") warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID elif not group_id: - group_id = 'default' if standalone else tbl_id - - payload = {'chartId': cid, 'chartType': 'scatter', - 'groupId': group_id, 'viewerId': group_id, - 'params': {'tbl_id': tbl_id, **chart_params}} - - r = self.dispatch(ACTION_DICT['ShowXYPlot'], payload) - warning and r.update({'warning': warning}) + group_id = "default" if standalone else tbl_id + + payload = { + "chartId": cid, + "chartType": "scatter", + "groupId": group_id, + "viewerId": group_id, + "params": {"tbl_id": tbl_id, **chart_params}, + } + + r = self.dispatch(ACTION_DICT["ShowXYPlot"], payload) + warning and r.update({"warning": warning}) return r - def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_params): + def show_histogram( + self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_params + ): """ Show a histogram @@ -1076,17 +1282,22 @@ def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_pa warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID - cid = gen_item_id('Histogram') - payload = {'chartId': cid, 'chartType': 'histogram', - 'groupId': group_id, - 'viewerId': group_id, - 'params': {'tbl_id': tbl_id, **histogram_params}} - - r = self.dispatch(ACTION_DICT['ShowXYPlot'], payload) - warning and r.update({'warning': warning}) + cid = gen_item_id("Histogram") + payload = { + "chartId": cid, + "chartType": "histogram", + "groupId": group_id, + "viewerId": group_id, + "params": {"tbl_id": tbl_id, **histogram_params}, + } + + r = self.dispatch(ACTION_DICT["ShowXYPlot"], payload) + warning and r.update({"warning": warning}) return r def show_chart(self, group_id=PINNED_CHART_VIEWER_ID, **chart_params): @@ -1124,25 +1335,33 @@ def show_chart(self, group_id=PINNED_CHART_VIEWER_ID, **chart_params): Status of the request, like {'success': True}. """ - chart_id = chart_params.get('chartId') if 'chartId' in chart_params else gen_item_id('Plotly') + chart_id = ( + chart_params.get("chartId") + if "chartId" in chart_params + else gen_item_id("Plotly") + ) warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID - payload = {'chartId': chart_id, - 'groupId': group_id, - 'viewerId': group_id, - 'chartType': 'plot.ly', - 'closable': True} - - for item in ['data', 'layout']: + payload = { + "chartId": chart_id, + "groupId": group_id, + "viewerId": group_id, + "chartType": "plot.ly", + "closable": True, + } + + for item in ["data", "layout"]: (item in chart_params) and payload.update({item: chart_params.get(item)}) - r = self.dispatch(ACTION_DICT['ShowPlot'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowPlot"], payload) + warning and r.update({"warning": warning}) return r - def show_coverage(self, viewer_id=None, table_group='main'): + def show_coverage(self, viewer_id=None, table_group="main"): """ Show image coverage associated with the active table in the specified table group @@ -1159,14 +1378,18 @@ def show_coverage(self, viewer_id=None, table_group='main'): Status of the request, like {'success': True} """ if self.is_triview(): - return {'success': True, 'warning': 'show_coverage ignored in triview mode'} + return {"success": True, "warning": "show_coverage ignored in triview mode"} - view_type = 'coverImage' - cid = viewer_id if viewer_id else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) - payload = {'viewerType': LO_VIEW_DICT[view_type], 'cellId': cid} - return self.dispatch(ACTION_DICT['ShowCoverage'], payload) + view_type = "coverImage" + cid = ( + viewer_id + if viewer_id + else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) + ) + payload = {"viewerType": LO_VIEW_DICT[view_type], "cellId": cid} + return self.dispatch(ACTION_DICT["ShowCoverage"], payload) - def show_image_metadata(self, viewer_id=None, table_group='main'): + def show_image_metadata(self, viewer_id=None, table_group="main"): """ Show the image associated with the active (image metadata) table in the specified table group @@ -1183,16 +1406,31 @@ def show_image_metadata(self, viewer_id=None, table_group='main'): Status of the request, like {'success': True} """ if self.is_triview(): - return {'success': True, 'warning': 'show_image_metadata ignored in triview mode'} - - view_type = 'imageMeta' - cid = viewer_id if viewer_id else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) - payload = {'viewerType': LO_VIEW_DICT[view_type], 'cellId': cid} - - return self.dispatch(ACTION_DICT['ShowImageMetaData'], payload) - - def add_extension(self, ext_type, plot_id=None, title='', tool_tip='', - shortcut_key='', extension_id=None, image_src=None): + return { + "success": True, + "warning": "show_image_metadata ignored in triview mode", + } + + view_type = "imageMeta" + cid = ( + viewer_id + if viewer_id + else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) + ) + payload = {"viewerType": LO_VIEW_DICT[view_type], "cellId": cid} + + return self.dispatch(ACTION_DICT["ShowImageMetaData"], payload) + + def add_extension( + self, + ext_type, + plot_id=None, + title="", + tool_tip="", + shortcut_key="", + extension_id=None, + image_src=None, + ): """ Add an extension to the plot. Extensions are context menus that allows you to extend what Firefly can do when certain actions happen. @@ -1226,17 +1464,22 @@ def add_extension(self, ext_type, plot_id=None, title='', tool_tip='', """ if not extension_id: - extension_id = gen_item_id('Extension') - payload = {'extension': { - 'id': extension_id, 'plotId': plot_id, - 'imageUrl': create_image_url(image_src) if image_src else None, - 'title': title, 'extType': ext_type, - 'toolTip': tool_tip, 'shortcutKey': shortcut_key} - } - return self.dispatch(ACTION_DICT['AddExtension'], payload) + extension_id = gen_item_id("Extension") + payload = { + "extension": { + "id": extension_id, + "plotId": plot_id, + "imageUrl": create_image_url(image_src) if image_src else None, + "title": title, + "extType": ext_type, + "toolTip": tool_tip, + "shortcutKey": shortcut_key, + } + } + return self.dispatch(ACTION_DICT["AddExtension"], payload) def table_highlight_callback(self, func, columns): - """ Set a user-defined callback for table highlights + """Set a user-defined callback for table highlights Parameters ---------- @@ -1258,15 +1501,16 @@ def table_highlight_callback(self, func, columns): ------- func : the callback function that was added """ + def highlight_callback(event): - if event['data'].get('type') == 'table.highlight': - absolute_row = int(event['data']['row']['ROW_IDX']) - relative_row = int(event['data']['row']['ROW_NUM']) - tbl_id = event.get('tbl_id') - col_data = copy(event['data']['row']) + if event["data"].get("type") == "table.highlight": + absolute_row = int(event["data"]["row"]["ROW_IDX"]) + relative_row = int(event["data"]["row"]["ROW_NUM"]) + tbl_id = event.get("tbl_id") + col_data = copy(event["data"]["row"]) if columns is not None: - col_data.pop('ROW_IDX') - col_data.pop('ROW_NUM') + col_data.pop("ROW_IDX") + col_data.pop("ROW_NUM") if len(columns) == 0: for k in col_data: col_data.pop(k) @@ -1274,12 +1518,19 @@ def highlight_callback(event): for k in columns: col_data.pop(k) func(absolute_row, relative_row, col_data, tbl_id) + self.add_listener(highlight_callback) - self.add_extension(ext_type='table.highlight', extension_id='table_highlight') + self.add_extension(ext_type="table.highlight", extension_id="table_highlight") return highlight_callback - def show_hips(self, plot_id=None, viewer_id=None, hips_root_url=None, hips_image_conversion=None, - **additional_params): + def show_hips( + self, + plot_id=None, + viewer_id=None, + hips_root_url=None, + hips_image_conversion=None, + **additional_params, + ): """ Show HiPS image. @@ -1314,33 +1565,45 @@ def show_hips(self, plot_id=None, viewer_id=None, hips_root_url=None, hips_image if not hips_root_url: return - wp_request = {'plotGroupId': 'groupFromPython', 'hipsRootUrl': hips_root_url} + wp_request = {"plotGroupId": "groupFromPython", "hipsRootUrl": hips_root_url} additional_params and wp_request.update(additional_params) - payload = {'wpRequest': wp_request} + payload = {"wpRequest": wp_request} if not plot_id: - plot_id = gen_item_id('Image') + plot_id = gen_item_id("Image") - payload.update({'plotId': plot_id}) - wp_request.update({'plotId': plot_id}) + payload.update({"plotId": plot_id}) + wp_request.update({"plotId": plot_id}) warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) + payload.update({"viewerId": viewer_id}) if hips_image_conversion and type(hips_image_conversion) is dict: - payload.update({'hipsImageConversion': hips_image_conversion}) + payload.update({"hipsImageConversion": hips_image_conversion}) - r = self.dispatch(ACTION_DICT['ShowHiPS'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowHiPS"], payload) + warning and r.update({"warning": warning}) return r - def show_image_or_hips(self, plot_id=None, viewer_id=None, image_request=None, hips_request=None, - fov_deg_fallover=0.12, allsky_request=None, plot_allsky_first=False): + def show_image_or_hips( + self, + plot_id=None, + viewer_id=None, + image_request=None, + hips_request=None, + fov_deg_fallover=0.12, + allsky_request=None, + plot_allsky_first=False, + ): """ Show a FiTS or HiPS image. @@ -1373,28 +1636,39 @@ def show_image_or_hips(self, plot_id=None, viewer_id=None, image_request=None, h return if not plot_id: - plot_id = gen_item_id('Image') + plot_id = gen_item_id("Image") warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload = {'fovDegFallOver': fov_deg_fallover, 'plotAllSkyFirst': plot_allsky_first, - 'plotId': plot_id, 'viewerId': viewer_id} - - pg_key = 'plotGroupId' - if not ((hips_request and hips_request.get(pg_key)) or (image_request and image_request.get(pg_key))): + payload = { + "fovDegFallOver": fov_deg_fallover, + "plotAllSkyFirst": plot_allsky_first, + "plotId": plot_id, + "viewerId": viewer_id, + } + + pg_key = "plotGroupId" + if not ( + (hips_request and hips_request.get(pg_key)) + or (image_request and image_request.get(pg_key)) + ): if hips_request: - hips_request.update({pg_key: 'groupFromPython'}) + hips_request.update({pg_key: "groupFromPython"}) elif image_request: - image_request.update({pg_key: 'groupFromPython'}) + image_request.update({pg_key: "groupFromPython"}) - image_request and payload.update({'imageRequest': image_request}) - hips_request and payload.update({'hipsRequest': hips_request}) - allsky_request and payload.update({'allSkyRequest': allsky_request}) + image_request and payload.update({"imageRequest": image_request}) + hips_request and payload.update({"hipsRequest": hips_request}) + allsky_request and payload.update({"allSkyRequest": allsky_request}) - r = self.dispatch(ACTION_DICT['ShowImageOrHiPS'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowImageOrHiPS"], payload) + warning and r.update({"warning": warning}) return r # ---------------------------- @@ -1420,15 +1694,20 @@ def set_zoom(self, plot_id, factor=1.0): """ def zoom_oneplot(one_plot_id, f): - payload = {'plotId': one_plot_id, 'userZoomType': 'LEVEL', 'level': f, 'actionScope': 'SINGLE'} - return self.dispatch(ACTION_DICT['ZoomImage'], payload) + payload = { + "plotId": one_plot_id, + "userZoomType": "LEVEL", + "level": f, + "actionScope": "SINGLE", + } + return self.dispatch(ACTION_DICT["ZoomImage"], payload) if isinstance(plot_id, tuple) or isinstance(plot_id, list): return [zoom_oneplot(x, factor) for x in plot_id] else: return zoom_oneplot(plot_id, factor) - def set_pan(self, plot_id, x=None, y=None, coord='image'): + def set_pan(self, plot_id, x=None, y=None, coord="image"): """ Relocate the image to center on the given image coordinate or EQ_J2000 coordinate. If no (x, y) is given, the image is re-centered at the center of the image. @@ -1451,15 +1730,15 @@ def set_pan(self, plot_id, x=None, y=None, coord='image'): Status of the request, like {'success': True}. """ - payload = {'plotId': plot_id} - if coord.startswith('image'): - payload.update({'centerOnImage': 'true'}) + payload = {"plotId": plot_id} + if coord.startswith("image"): + payload.update({"centerOnImage": "true"}) elif x and y: - payload.update({'centerPt': f'{x};{y};{coord}'}) + payload.update({"centerPt": f"{x};{y};{coord}"}) + + return self.dispatch(ACTION_DICT["PanImage"], payload) - return self.dispatch(ACTION_DICT['PanImage'], payload) - - def align_images(self, match_type='Standard', lock_match=False): + def align_images(self, match_type="Standard", lock_match=False): """ Align the images being displayed. @@ -1477,12 +1756,14 @@ def align_images(self, match_type='Standard', lock_match=False): ------- out : `dict` Status of the request, like {'success': True}. - + """ payload = dict(matchType=match_type, lockMatch=lock_match) - return self.dispatch(ACTION_DICT['AlignImages'], payload) + return self.dispatch(ACTION_DICT["AlignImages"], payload) - def set_stretch(self, plot_id, stype=None, algorithm=None, band=None, **additional_params): + def set_stretch( + self, plot_id, stype=None, algorithm=None, band=None, **additional_params + ): """ Change the stretch of the image (no band or 3-color per-band cases). @@ -1528,27 +1809,35 @@ def set_stretch(self, plot_id, stype=None, algorithm=None, band=None, **addition `stype` is 'zscale', and `lower_value` and `upper_value` are used when `stype` is not 'zscale'. """ - serialized_rv = RangeValues.create_rv_by_stretch_type(algorithm, stype, **additional_params) - bands_3color = ['RED', 'GREEN', 'BLUE', 'ALL'] + serialized_rv = RangeValues.create_rv_by_stretch_type( + algorithm, stype, **additional_params + ) + bands_3color = ["RED", "GREEN", "BLUE", "ALL"] if not band: - band_list = ['NO_BAND'] + band_list = ["NO_BAND"] elif band in bands_3color: - band_list = ['RED', 'GREEN', 'BLUE'] if band == 'ALL' else [band] + band_list = ["RED", "GREEN", "BLUE"] if band == "ALL" else [band] else: - raise ValueError('invalid band: %s' % band) + raise ValueError("invalid band: %s" % band) st_data = [] for b in band_list: - st_data.append({'band': b, 'rv': serialized_rv, 'bandVisible': True}) + st_data.append({"band": b, "rv": serialized_rv, "bandVisible": True}) - payload = {'stretchData': st_data, 'plotId': plot_id} + payload = {"stretchData": st_data, "plotId": plot_id} - return_val = self.dispatch(ACTION_DICT['StretchImage'], payload) - return_val['rv_string'] = serialized_rv + return_val = self.dispatch(ACTION_DICT["StretchImage"], payload) + return_val["rv_string"] = serialized_rv return return_val - def set_stretch_hprgb(self, plot_id, asinh_q_value=None, scaling_k=1.0, - pedestal_value=1, pedestal_type='percent'): + def set_stretch_hprgb( + self, + plot_id, + asinh_q_value=None, + scaling_k=1.0, + pedestal_value=1, + pedestal_type="percent", + ): """ Change the stretch of RGB image (hue-preserving rgb case). When a parameter is a list, it must contain three elements, for red, green and blue bands respectively. @@ -1578,28 +1867,30 @@ def set_stretch_hprgb(self, plot_id, asinh_q_value=None, scaling_k=1.0, .. note:: `pedestal_value` is used when `pedestal_type` is not 'zscale'. """ - scaling_k = ensure3(scaling_k, 'scaling_k') - pedestal_type = ensure3(pedestal_type, 'pedestal_type') - pedestal_value = ensure3(pedestal_value, 'pedestal_value') + scaling_k = ensure3(scaling_k, "scaling_k") + pedestal_type = ensure3(pedestal_type, "pedestal_type") + pedestal_value = ensure3(pedestal_value, "pedestal_value") st_data = [] - bands = ['RED', 'GREEN', 'BLUE'] + bands = ["RED", "GREEN", "BLUE"] for i, band in enumerate(bands): - serialized_rv = RangeValues.create_rv(stretch_type=pedestal_type[i], - lower_value=pedestal_value[i], - upper_value=99.0, - algorithm='asinh', - asinh_q_value=asinh_q_value, - rgb_preserve_hue=1, - scaling_k=scaling_k[i]) - st_data.append({'band': band, 'rv': serialized_rv, 'bandVisible': True}) - - payload = {'stretchData': st_data, 'plotId': plot_id} - return_val = self.dispatch(ACTION_DICT['StretchImage'], payload) - return_val['rv_lst'] = [d['rv'] for d in st_data] + serialized_rv = RangeValues.create_rv( + stretch_type=pedestal_type[i], + lower_value=pedestal_value[i], + upper_value=99.0, + algorithm="asinh", + asinh_q_value=asinh_q_value, + rgb_preserve_hue=1, + scaling_k=scaling_k[i], + ) + st_data.append({"band": band, "rv": serialized_rv, "bandVisible": True}) + + payload = {"stretchData": st_data, "plotId": plot_id} + return_val = self.dispatch(ACTION_DICT["StretchImage"], payload) + return_val["rv_lst"] = [d["rv"] for d in st_data] return return_val - def set_color(self, plot_id, colormap_id=0, bias=.5, contrast=1): + def set_color(self, plot_id, colormap_id=0, bias=0.5, contrast=1): """ Change the color attributes (color map, bias, constrast) of an image plot. @@ -1624,15 +1915,23 @@ def set_color(self, plot_id, colormap_id=0, bias=.5, contrast=1): .. note:: when `colormap_id` is -1 for HiPS image, `contrast` and `bias` have no effect. """ - payload = {'plotId': plot_id, - 'cbarId': colormap_id, - 'bias': bias, - 'contrast': contrast - } - return self.dispatch(ACTION_DICT['ColorImage'], payload) - - def set_rgb_colors(self, plot_id, use_red=True, use_green=True, use_blue=True, - bias=[.5,.5,.5], contrast=[1,1,1]): + payload = { + "plotId": plot_id, + "cbarId": colormap_id, + "bias": bias, + "contrast": contrast, + } + return self.dispatch(ACTION_DICT["ColorImage"], payload) + + def set_rgb_colors( + self, + plot_id, + use_red=True, + use_green=True, + use_blue=True, + bias=[0.5, 0.5, 0.5], + contrast=[1, 1, 1], + ): """ Change the color attributes of a 3-color fits image plot. @@ -1656,14 +1955,15 @@ def set_rgb_colors(self, plot_id, use_red=True, use_green=True, use_blue=True, out : `dict` Status of the request, like {'success': True}. """ - payload = {'plotId': plot_id, - 'useRed': use_red, - 'useGreen': use_green, - 'useBlue': use_blue, - 'bias': bias, - 'contrast': contrast - } - return self.dispatch(ACTION_DICT['ColorImage'], payload) + payload = { + "plotId": plot_id, + "useRed": use_red, + "useGreen": use_green, + "useBlue": use_blue, + "bias": bias, + "contrast": contrast, + } + return self.dispatch(ACTION_DICT["ColorImage"], payload) @staticmethod def parse_rvstring(rvstring): @@ -1700,8 +2000,16 @@ def rvstring_from_dict(rvdict): # ----------------------------------------------------------------- # image line based footprint overlay # ----------------------------------------------------------------- - def overlay_footprints(self, footprint_file, footprint_image=None, title=None, - footprint_layer_id=None, plot_id=None, table_index=None, **additional_params): + def overlay_footprints( + self, + footprint_file, + footprint_image=None, + title=None, + footprint_layer_id=None, + plot_id=None, + table_index=None, + **additional_params, + ): """ Overlay a footprint dictionary on displayed images. The dictionary must be convertible to JSON format. @@ -1745,23 +2053,29 @@ def overlay_footprints(self, footprint_file, footprint_image=None, title=None, """ if not footprint_layer_id: - footprint_layer_id = gen_item_id('FootprintLayer') - payload = {'drawLayerId': footprint_layer_id} - - title and payload.update({'title': title}) - plot_id and payload.update({'plotId': plot_id}) - footprint_file and payload.update({'footprintFile': footprint_file}) - footprint_image and payload.update({'footprintImageFile': footprint_image}) - table_index and payload.update({'tbl_index': table_index}) + footprint_layer_id = gen_item_id("FootprintLayer") + payload = {"drawLayerId": footprint_layer_id} + + title and payload.update({"title": title}) + plot_id and payload.update({"plotId": plot_id}) + footprint_file and payload.update({"footprintFile": footprint_file}) + footprint_image and payload.update({"footprintImageFile": footprint_image}) + table_index and payload.update({"tbl_index": table_index}) additional_params and payload.update(additional_params) - return self.dispatch(ACTION_DICT['ImagelineBasedFootprint'], payload) + return self.dispatch(ACTION_DICT["ImagelineBasedFootprint"], payload) # ----------------------------------------------------------------- # Region Stuff # ----------------------------------------------------------------- - def overlay_region_layer(self, file_on_server=None, region_data=None, title=None, - region_layer_id=None, plot_id=None): + def overlay_region_layer( + self, + file_on_server=None, + region_data=None, + title=None, + region_layer_id=None, + plot_id=None, + ): """ Overlay a region layer on the loaded FITS images. The regions are defined either by a file or by text region description. @@ -1793,17 +2107,17 @@ def overlay_region_layer(self, file_on_server=None, region_data=None, title=None """ if not region_layer_id: - region_layer_id = gen_item_id('RegionLayer') - payload = {'drawLayerId': region_layer_id} + region_layer_id = gen_item_id("RegionLayer") + payload = {"drawLayerId": region_layer_id} - title and payload.update({'layerTitle': title}) - plot_id and payload.update({'plotId': plot_id}) + title and payload.update({"layerTitle": title}) + plot_id and payload.update({"plotId": plot_id}) if file_on_server: - payload.update({'fileOnServer': file_on_server}) + payload.update({"fileOnServer": file_on_server}) elif region_data: - payload.update({'regionAry': region_data}) + payload.update({"regionAry": region_data}) - return self.dispatch(ACTION_DICT['CreateRegionLayer'], payload) + return self.dispatch(ACTION_DICT["CreateRegionLayer"], payload) def delete_region_layer(self, region_layer_id, plot_id=None): """ @@ -1823,9 +2137,9 @@ def delete_region_layer(self, region_layer_id, plot_id=None): Status of the request, like {'success': True}. """ - payload = {'drawLayerId': region_layer_id} - plot_id and payload.update({'plotId': plot_id}) - return self.dispatch(ACTION_DICT['DeleteRegionLayer'], payload) + payload = {"drawLayerId": region_layer_id} + plot_id and payload.update({"plotId": plot_id}) + return self.dispatch(ACTION_DICT["DeleteRegionLayer"], payload) def add_region_data(self, region_data, region_layer_id, title=None, plot_id=None): """ @@ -1854,10 +2168,10 @@ def add_region_data(self, region_data, region_layer_id, title=None, plot_id=None """ - payload = {'regionChanges': region_data, 'drawLayerId': region_layer_id} - plot_id and payload.update({'plotId': plot_id}) - title and payload.update({'layerTitle': title}) - return self.dispatch(ACTION_DICT['AddRegionData'], payload) + payload = {"regionChanges": region_data, "drawLayerId": region_layer_id} + plot_id and payload.update({"plotId": plot_id}) + title and payload.update({"layerTitle": title}) + return self.dispatch(ACTION_DICT["AddRegionData"], payload) def remove_region_data(self, region_data, region_layer_id): """ @@ -1875,12 +2189,20 @@ def remove_region_data(self, region_data, region_layer_id): out : `dict` Status of the request, like {'success': True}. """ - payload = {'regionChanges': region_data, 'drawLayerId': region_layer_id} + payload = {"regionChanges": region_data, "drawLayerId": region_layer_id} - return self.dispatch(ACTION_DICT['RemoveRegionData'], payload) + return self.dispatch(ACTION_DICT["RemoveRegionData"], payload) - def add_mask(self, bit_number, image_number, plot_id, mask_id=None, color=None, title=None, - file_on_server=None): + def add_mask( + self, + bit_number, + image_number, + plot_id, + mask_id=None, + color=None, + title=None, + file_on_server=None, + ): """ Add a mask layer. @@ -1909,15 +2231,21 @@ def add_mask(self, bit_number, image_number, plot_id, mask_id=None, color=None, """ if not mask_id: - mask_id = gen_item_id('MaskLayer') + mask_id = gen_item_id("MaskLayer") if not title: - title = 'bit %23 ' + str(bit_number) - - payload = {'plotId': plot_id, 'imageOverlayId': mask_id, 'imageNumber': image_number, - 'maskNumber': bit_number, 'maskValue': int(math.pow(2, bit_number)), 'title': title} - color and payload.update({'color': color}) - file_on_server and payload.update({'fileKey': file_on_server}) - return self.dispatch(ACTION_DICT['PlotMask'], payload) + title = "bit %23 " + str(bit_number) + + payload = { + "plotId": plot_id, + "imageOverlayId": mask_id, + "imageNumber": image_number, + "maskNumber": bit_number, + "maskValue": int(math.pow(2, bit_number)), + "title": title, + } + color and payload.update({"color": color}) + file_on_server and payload.update({"fileKey": file_on_server}) + return self.dispatch(ACTION_DICT["PlotMask"], payload) def remove_mask(self, plot_id, mask_id): """ @@ -1936,8 +2264,8 @@ def remove_mask(self, plot_id, mask_id): Status of the request, like {'success': True} """ - payload = {'plotId': plot_id, 'imageOverlayId': mask_id} - return self.dispatch(ACTION_DICT['DeleteOverlayMask'], payload) + payload = {"plotId": plot_id, "imageOverlayId": mask_id} + return self.dispatch(ACTION_DICT["DeleteOverlayMask"], payload) # ---------------------------- # actions on table @@ -1960,11 +2288,11 @@ def apply_table_filters(self, tbl_id, filters): out : `dict` Status of the request, like {'success': True} """ - tbl_req = {'tbl_id': tbl_id, 'filters': filters} - payload = {'request': tbl_req} - return self.dispatch(ACTION_DICT['TableFilter'], payload) - - def sort_table_column(self, tbl_id, column_name, sort_direction=''): + tbl_req = {"tbl_id": tbl_id, "filters": filters} + payload = {"request": tbl_req} + return self.dispatch(ACTION_DICT["TableFilter"], payload) + + def sort_table_column(self, tbl_id, column_name, sort_direction=""): """ Sort a loaded table by a given column name. @@ -1975,7 +2303,7 @@ def sort_table_column(self, tbl_id, column_name, sort_direction=''): column_name : `str` Name of the table column to sort sort_direction : {'', 'ASC', 'DESC'}, optional - Direction of sort: '' for unsorted (or for removing the sort), + Direction of sort: '' for unsorted (or for removing the sort), 'ASC' for ascending, and 'DESC' for descending. Default is ''. Returns @@ -1983,10 +2311,12 @@ def sort_table_column(self, tbl_id, column_name, sort_direction=''): out : `dict` Status of the request, like {'success': True} """ - sort_directions = ['', 'ASC', 'DESC'] + sort_directions = ["", "ASC", "DESC"] if sort_direction not in sort_directions: - raise ValueError(f'Invalid sort_direction. Valid values are {sort_directions}') - - tbl_req = {'tbl_id': tbl_id, 'sortInfo': f'{sort_direction},{column_name}'} - payload = {'request': tbl_req} - return self.dispatch(ACTION_DICT['TableSort'], payload) + raise ValueError( + f"Invalid sort_direction. Valid values are {sort_directions}" + ) + + tbl_req = {"tbl_id": tbl_id, "sortInfo": f"{sort_direction},{column_name}"} + payload = {"request": tbl_req} + return self.dispatch(ACTION_DICT["TableSort"], payload) diff --git a/firefly_client/plot.py b/firefly_client/plot.py index bd2dc04..561a926 100644 --- a/firefly_client/plot.py +++ b/firefly_client/plot.py @@ -7,22 +7,22 @@ variable FIREFLY_URL. """ -import logging import os +import logging import tempfile -import time -from .firefly_client import FireflyClient + from .fc_utils import gen_item_id + logger = logging.getLogger(__name__) fc = None last_tblid = None last_imageid = None # Set up default cells -table_cellid = 'tables' -plots_cellid = 'plots' -images_cellid = 'images' +table_cellid = "tables" +plots_cellid = "plots" +images_cellid = "images" def use_client(ffclient): @@ -42,7 +42,9 @@ def use_client(ffclient): def _confirm_fc(): if fc is None: - raise ValueError('a FireflyClient instance has not been defined, define it first with use_client()') + raise ValueError( + "a FireflyClient instance has not been defined, define it first with use_client()" + ) def reset_layout(): @@ -52,14 +54,19 @@ def reset_layout(): row containing plots in the first column and images in the second column. """ _confirm_fc() - fc.add_cell(row=0, col=0, width=2, height=2, element_type='tables', cell_id=table_cellid) - fc.add_cell(row=2, col=0, width=1, height=2, element_type='xyPlots', cell_id=plots_cellid) - fc.add_cell(row=2, col=1, width=1, height=2, element_type='images', cell_id=images_cellid) + fc.add_cell( + row=0, col=0, width=2, height=2, element_type="tables", cell_id=table_cellid + ) + fc.add_cell( + row=2, col=0, width=1, height=2, element_type="xyPlots", cell_id=plots_cellid + ) + fc.add_cell( + row=2, col=1, width=1, height=2, element_type="images", cell_id=images_cellid + ) def display_url(): - """Display the web viewer URL, if possible as a clickable link - """ + """Display the web viewer URL, if possible as a clickable link""" _confirm_fc() fc.display_url() @@ -77,14 +84,24 @@ def open_browser(force=False): def clear(): - """Clear the web viewer - """ + """Clear the web viewer""" _confirm_fc() fc.reinit_viewer() -def scatter(x_col, y_col, tbl_id='', size=4, color=None, alpha=1.0, - title='', xlabel=None, ylabel=None, cell_id=plots_cellid, **kwargs): +def scatter( + x_col, + y_col, + tbl_id="", + size=4, + color=None, + alpha=1.0, + title="", + xlabel=None, + ylabel=None, + cell_id=plots_cellid, + **kwargs, +): """Make a scatter plot from a table uploaded to Firefly Parameters: @@ -114,24 +131,40 @@ def scatter(x_col, y_col, tbl_id='', size=4, color=None, alpha=1.0, """ _confirm_fc() - layout = dict(xaxis=dict(title=xlabel if xlabel else x_col), - yaxis=dict(title=ylabel if ylabel else y_col)) + layout = dict( + xaxis=dict(title=xlabel if xlabel else x_col), + yaxis=dict(title=ylabel if ylabel else y_col), + ) if title is not None: - layout['title'] = title + layout["title"] = title if len(tbl_id) == 0: - logger.debug('Using last_tblid: {}'.format(last_tblid)) + logger.debug("Using last_tblid: {}".format(last_tblid)) tbl_id = last_tblid marker_dict = dict(size=size, opacity=alpha) if color: - marker_dict['color'] = color - trace = dict(tbl_id=tbl_id, x='tables::' + x_col, y='tables::' + y_col, - mode='markers', type='scatter', marker=marker_dict) + marker_dict["color"] = color + trace = dict( + tbl_id=tbl_id, + x="tables::" + x_col, + y="tables::" + y_col, + mode="markers", + type="scatter", + marker=marker_dict, + ) trace.update(kwargs) fc.show_chart(layout=layout, data=[trace], group_id=cell_id) -def hist(data_col, tbl_id='', nbins=30, title='', xlabel=None, - ylabel=None, cell_id=plots_cellid, **kwargs): +def hist( + data_col, + tbl_id="", + nbins=30, + title="", + xlabel=None, + ylabel=None, + cell_id=plots_cellid, + **kwargs, +): """Make a histogram from a table uploaded to Firefly data_col: `str` @@ -150,34 +183,43 @@ def hist(data_col, tbl_id='', nbins=30, title='', xlabel=None, ID of Slate cell from add_cell, defaults to table_cellid """ _confirm_fc() - layout = dict(xaxis=dict(title=xlabel if xlabel else data_col), - yaxis=dict(title=ylabel if ylabel else 'Number')) + layout = dict( + xaxis=dict(title=xlabel if xlabel else data_col), + yaxis=dict(title=ylabel if ylabel else "Number"), + ) if title is not None: - layout['title'] = title + layout["title"] = title if len(tbl_id) == 0: - logger.debug('Using last_tblid: {}'.format(last_tblid)) + logger.debug("Using last_tblid: {}".format(last_tblid)) tbl_id = last_tblid hist_data = dict( - type='fireflyHistogram', + type="fireflyHistogram", name=data_col, - marker={'color': 'rgba(153, 51, 153, 0.8)'}, + marker={"color": "rgba(153, 51, 153, 0.8)"}, firefly=dict( tbl_id=tbl_id, options=dict( - algorithm='fixedSizeBins', - fixedBinSizeSelection='numBins', + algorithm="fixedSizeBins", + fixedBinSizeSelection="numBins", numBins=nbins, - columnOrExpr=data_col - ) - ) + columnOrExpr=data_col, + ), + ), ) hist_data.update(kwargs) fc.show_chart(group_id=cell_id, layout=layout, data=[hist_data]) return -def upload_table(table, title=None, show=True, write_func="auto", tbl_index=1, page_size=200, - view_coverage=False): +def upload_table( + table, + title=None, + show=True, + write_func="auto", + tbl_index=1, + page_size=200, + view_coverage=False, +): """upload a table object to Firefly Parameters: @@ -205,47 +247,57 @@ def upload_table(table, title=None, show=True, write_func="auto", tbl_index=1, p if isinstance(table, str): tval = fc.upload_file(table) else: - if write_func == 'auto': - if 'lsst.afw.table' in str(type(table)): + if write_func == "auto": + if "lsst.afw.table" in str(type(table)): write_func = table.writeFits - elif 'astropy.table.table.Table' in str(type(table)): + elif "astropy.table.table.Table" in str(type(table)): + def write_astropy(fname): atable = table.copy() for c in atable.colnames: if len(c) > 68: atable.rename_column(c, c[:68]) import warnings + from astropy.utils.exceptions import AstropyWarning + with warnings.catch_warnings(): - warnings.simplefilter('ignore', AstropyWarning) - atable.write(fname, format='fits', overwrite=True) + warnings.simplefilter("ignore", AstropyWarning) + atable.write(fname, format="fits", overwrite=True) + write_func = write_astropy else: - raise RuntimeError('Unable to auto-discover output method for ' + str(type(table))) - with tempfile.NamedTemporaryFile(delete=False, suffix='.fits') as fd: + raise RuntimeError( + "Unable to auto-discover output method for " + str(type(table)) + ) + with tempfile.NamedTemporaryFile(delete=False, suffix=".fits") as fd: write_func(fd.name) tval = fc.upload_file(fd.name) - logger.debug('Image name is {}'.format(fd.name)) + logger.debug("Image name is {}".format(fd.name)) os.remove(fd.name) if title is None: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") else: tbl_id = title if view_coverage: coverage() if show: - status = fc.show_table(tval, tbl_id=tbl_id, title=title, table_index=tbl_index, page_size=page_size) + status = fc.show_table( + tval, tbl_id=tbl_id, title=title, table_index=tbl_index, page_size=page_size + ) else: - status = fc.fetch_table(tval, tbl_id=tbl_id, table_index=tbl_index, page_size=page_size) - if status['success']: + status = fc.fetch_table( + tval, tbl_id=tbl_id, table_index=tbl_index, page_size=page_size + ) + if status["success"]: global last_tblid last_tblid = tbl_id return tbl_id else: - raise RuntimeError('table upload unsuccessful') + raise RuntimeError("table upload unsuccessful") -def upload_image(image, title=None, write_func='auto', cell_id=images_cellid): +def upload_image(image, title=None, write_func="auto", cell_id=images_cellid): """display an array or astropy.io.fits HDU or HDUList Parameters: @@ -266,40 +318,47 @@ def upload_image(image, title=None, write_func='auto', cell_id=images_cellid): if isinstance(image, str): fval = fc.upload_file(image) else: - if write_func == 'auto': - if 'lsst.afw.image' in str(type(image)): + if write_func == "auto": + if "lsst.afw.image" in str(type(image)): write_func = image.writeFits - elif 'astropy.io.fits' in str(type(image)): + elif "astropy.io.fits" in str(type(image)): + def write_astropy_image(fname): import warnings + from astropy.utils.exceptions import AstropyWarning + with warnings.catch_warnings(): - warnings.simplefilter('ignore', AstropyWarning) + warnings.simplefilter("ignore", AstropyWarning) image.writeto(fname, overwrite=True) + write_func = write_astropy_image - elif 'numpy.ndarray' in str(type(image)): + elif "numpy.ndarray" in str(type(image)): import astropy.io.fits + hdu = astropy.io.fits.PrimaryHDU(data=image) - write_func = lambda fname: hdu.writeto(fname, overwrite=True) + write_func = lambda fname: hdu.writeto(fname, overwrite=True) # noqa E731 else: - raise RuntimeError('Unable to auto-discover output method for ' + str(type(table))) - with tempfile.NamedTemporaryFile(delete=False, suffix='.fits') as fd: + raise RuntimeError( + "Unable to auto-discover output method for " + str(type(table)) # noqa F821 + ) + with tempfile.NamedTemporaryFile(delete=False, suffix=".fits") as fd: write_func(fd.name) fval = fc.upload_file(fd.name) - logger.debug('Image name is {}'.format(fd.name)) + logger.debug("Image name is {}".format(fd.name)) os.remove(fd.name) if title is None: - image_id = gen_item_id('Image') + image_id = gen_item_id("Image") title = image_id else: image_id = title status = fc.show_fits(fval, plot_id=image_id, title=title, viewer_id=cell_id) - if status['success']: + if status["success"]: global last_imageid last_imageid = image_id return image_id else: - raise RuntimeError('image upload and display unsuccessful') + raise RuntimeError("image upload and display unsuccessful") def coverage(cell_id=images_cellid): diff --git a/firefly_client/range_values.py b/firefly_client/range_values.py index b3beb9a..8883a15 100644 --- a/firefly_client/range_values.py +++ b/firefly_client/range_values.py @@ -1,69 +1,119 @@ import math +from typing import ClassVar class RangeValues: # for serializing the RangeValues object - STRETCH_TYPE_DICT = {'percent': 88, 'minmax': 89, 'absolute': 90, 'zscale': 91, 'sigma': 92} + STRETCH_TYPE_DICT: ClassVar = { + "percent": 88, + "minmax": 89, + "absolute": 90, + "zscale": 91, + "sigma": 92, + } """Definition of stretch type (`dict`).""" - INVERSE_STRETCH_TYPE = {v: k for k, v in STRETCH_TYPE_DICT.items()} + INVERSE_STRETCH_TYPE: ClassVar = {v: k for k, v in STRETCH_TYPE_DICT.items()} - STRETCH_ALGORITHM_DICT = {'linear': 44, 'log': 45, 'loglog': 46, 'equal': 47, 'squared': 48, 'sqrt': 49, - 'asinh': 50, 'powerlaw_gamma': 51} + STRETCH_ALGORITHM_DICT: ClassVar = { + "linear": 44, + "log": 45, + "loglog": 46, + "equal": 47, + "squared": 48, + "sqrt": 49, + "asinh": 50, + "powerlaw_gamma": 51, + } """Definition of stretch algorithm (`dict`).""" - INVERSE_STRETCH_ALGORITHM = {v: k for k, v in STRETCH_ALGORITHM_DICT.items()} + INVERSE_STRETCH_ALGORITHM: ClassVar = { + v: k for k, v in STRETCH_ALGORITHM_DICT.items() + } @classmethod - def create_rv(cls, stretch_type, lower_value, upper_value, algorithm, - zscale_contrast=25, zscale_samples=600, zscale_samples_perline=120, - asinh_q_value=None, gamma_value=2.0, - rgb_preserve_hue=0, asinh_stretch=None, scaling_k=1.0): + def create_rv( + cls, + stretch_type, + lower_value, + upper_value, + algorithm, + zscale_contrast=25, + zscale_samples=600, + zscale_samples_perline=120, + asinh_q_value=None, + gamma_value=2.0, + rgb_preserve_hue=0, + asinh_stretch=None, + scaling_k=1.0, + ): retval = None st = stretch_type.lower() a = algorithm.lower() # when q is NaN (case-sensitive), Firefly will calculate q using range if asinh_q_value is None or math.isnan(asinh_q_value): - qstr = 'NaN' + qstr = "NaN" elif math.isinf(asinh_q_value): - raise ValueError('invalid asinh_q_value: %f' % asinh_q_value) + raise ValueError("invalid asinh_q_value: %f" % asinh_q_value) else: - qstr = '%f' % asinh_q_value + qstr = "%f" % asinh_q_value # when asinh_stretch is NaN (case-sensitive), Firefly will calculate asinh_stretch # for hue-preserving rgb using z-scale range of intensity if asinh_stretch is None or math.isnan(asinh_stretch): - asinh_stretch_str = 'NaN' + asinh_stretch_str = "NaN" elif math.isinf(asinh_stretch) or asinh_stretch < 0: - raise ValueError('invalid asinh_stretch for hue-preserving rgb: %f' % asinh_stretch) + raise ValueError( + "invalid asinh_stretch for hue-preserving rgb: %f" % asinh_stretch + ) else: - asinh_stretch_str = '%f' % asinh_stretch + asinh_stretch_str = "%f" % asinh_stretch if rgb_preserve_hue is None: rgb_preserve_hue = 0 if st in cls.STRETCH_TYPE_DICT and a in cls.STRETCH_ALGORITHM_DICT: - retval = '%d,%f,%d,%f,%s,%f,%d,%d,%d,%d,%d,%s,%f' % \ - (cls.STRETCH_TYPE_DICT[st], lower_value, - cls.STRETCH_TYPE_DICT[st], upper_value, - qstr, gamma_value, - cls.STRETCH_ALGORITHM_DICT[a], - zscale_contrast, zscale_samples, zscale_samples_perline, - rgb_preserve_hue, asinh_stretch_str, scaling_k) + retval = "%d,%f,%d,%f,%s,%f,%d,%d,%d,%d,%d,%s,%f" % ( + cls.STRETCH_TYPE_DICT[st], + lower_value, + cls.STRETCH_TYPE_DICT[st], + upper_value, + qstr, + gamma_value, + cls.STRETCH_ALGORITHM_DICT[a], + zscale_contrast, + zscale_samples, + zscale_samples_perline, + rgb_preserve_hue, + asinh_stretch_str, + scaling_k, + ) return retval @classmethod def create_rv_by_stretch_type(cls, algorithm, stretch_type, **additional_params): - a = algorithm.lower() if algorithm else 'linear' - st = stretch_type.lower() if stretch_type else 'percent' - if st == 'zscale': + a = algorithm.lower() if algorithm else "linear" + st = stretch_type.lower() if stretch_type else "percent" + if st == "zscale": return cls.create_rv_zscale(a, **additional_params) - elif st in ['minmax', 'maxmin']: # 'maxmin' retained for backwards compatibility - return cls.create_rv_standard(a, 'percent', lower_value=0, upper_value=100, **additional_params) + elif st in [ + "minmax", + "maxmin", + ]: # 'maxmin' retained for backwards compatibility + return cls.create_rv_standard( + a, "percent", lower_value=0, upper_value=100, **additional_params + ) else: return cls.create_rv_standard(a, stretch_type, **additional_params) @classmethod - def create_rv_standard(cls, algorithm, stretch_type='Percent', lower_value=1, upper_value=99, **additional_params): + def create_rv_standard( + cls, + algorithm, + stretch_type="Percent", + lower_value=1, + upper_value=99, + **additional_params, + ): """ Create range values for non-zscale cases. @@ -93,16 +143,32 @@ def create_rv_standard(cls, algorithm, stretch_type='Percent', lower_value=1, up out : `str` a serialized range values string """ - retval = cls.create_rv(stretch_type, lower_value, upper_value, algorithm, **additional_params) + retval = cls.create_rv( + stretch_type, lower_value, upper_value, algorithm, **additional_params + ) if not retval: - t = stretch_type if stretch_type.lower() in cls.STRETCH_TYPE_DICT else 'percent' - a = algorithm if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT else 'linear' + t = ( + stretch_type + if stretch_type.lower() in cls.STRETCH_TYPE_DICT + else "percent" + ) + a = ( + algorithm + if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT + else "linear" + ) retval = cls.create_rv(t, 1, 99, a, **additional_params) return retval @classmethod - def create_rv_zscale(cls, algorithm, zscale_contrast=25, - zscale_samples=600, zscale_samples_perline=120, **additional_params): + def create_rv_zscale( + cls, + algorithm, + zscale_contrast=25, + zscale_samples=600, + zscale_samples_perline=120, + **additional_params, + ): """ Create range values for zscale case. @@ -132,11 +198,23 @@ def create_rv_zscale(cls, algorithm, zscale_contrast=25, out : `str` a serialized range values string """ - retval = RangeValues.create_rv('zscale', 1, 1, algorithm, zscale_contrast, zscale_samples, - zscale_samples_perline, **additional_params) + retval = RangeValues.create_rv( + "zscale", + 1, + 1, + algorithm, + zscale_contrast, + zscale_samples, + zscale_samples_perline, + **additional_params, + ) if not retval: - a = algorithm if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT else 'linear' - retval = cls.create_rv('zscale', 1, 2, a, 25, 600, 120, **additional_params) + a = ( + algorithm + if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT + else "linear" + ) + retval = cls.create_rv("zscale", 1, 2, a, 25, 600, 120, **additional_params) return retval @classmethod @@ -153,22 +231,24 @@ def parse_rvstring(cls, rvstring): outdict : `dict` dictionary of the inputs """ - vals = rvstring.split(',') + vals = rvstring.split(",") assert 10 <= len(vals) <= 13 - outdict = dict(lower_type=cls.INVERSE_STRETCH_TYPE[int(vals[0])], - lower_value=float(vals[1]), - upper_type=cls.INVERSE_STRETCH_TYPE[int(vals[2])], - upper_value=float(vals[3]), - asinh_q_value=float(vals[4]), - gamma_value=float(vals[5]), - algorithm=cls.INVERSE_STRETCH_ALGORITHM[int(vals[6])], - zscale_contrast=int(vals[7]), - zscale_samples=int(vals[8]), - zscale_samples_perline=int(vals[9])) + outdict = dict( + lower_type=cls.INVERSE_STRETCH_TYPE[int(vals[0])], + lower_value=float(vals[1]), + upper_type=cls.INVERSE_STRETCH_TYPE[int(vals[2])], + upper_value=float(vals[3]), + asinh_q_value=float(vals[4]), + gamma_value=float(vals[5]), + algorithm=cls.INVERSE_STRETCH_ALGORITHM[int(vals[6])], + zscale_contrast=int(vals[7]), + zscale_samples=int(vals[8]), + zscale_samples_perline=int(vals[9]), + ) if len(vals) > 10: - outdict['rgb_preserve_hue'] = int(vals[10]) - outdict['asinh_stretch'] = float(vals[11]) - outdict['scaling_k'] = float(vals[12]) + outdict["rgb_preserve_hue"] = int(vals[10]) + outdict["asinh_stretch"] = float(vals[11]) + outdict["scaling_k"] = float(vals[12]) return outdict @classmethod @@ -186,8 +266,19 @@ def rvstring_from_dict(cls, rvdict): RangeValues string that can be passed to the show_fits methods """ - argnames = ['lower_value', 'upper_value', 'upper_value', 'algorithm', - 'zscale_contrast', 'zscale_samples', 'zscale_samples_perline', - 'asinh_q_value', 'gamma_value', 'rgb_preserve_hue', 'asinh_stretch', 'scaling_k'] + argnames = [ + "lower_value", + "upper_value", + "upper_value", + "algorithm", + "zscale_contrast", + "zscale_samples", + "zscale_samples_perline", + "asinh_q_value", + "gamma_value", + "rgb_preserve_hue", + "asinh_stretch", + "scaling_k", + ] kw = dict((k, rvdict[k]) for k in argnames) - return RangeValues.create_rv(stretch_type=rvdict['lower_type'], **kw) + return RangeValues.create_rv(stretch_type=rvdict["lower_type"], **kw) diff --git a/pyproject.toml b/pyproject.toml index c82ae09..1fe8b11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,16 +6,11 @@ build-backend = "setuptools.build_meta" name = "firefly_client" version = "3.3.0" description = "Python API for Firefly: display astronomical data as tables, images, charts, and more!" -authors = [ - {name = "IPAC LSST SUIT"} -] +authors = [{ name = "IPAC LSST SUIT" }] readme = "README.md" -license = {file = "License.txt"} +license = { file = "License.txt" } requires-python = ">=3.10" -dependencies = [ - "websocket-client", - "requests" -] +dependencies = ["websocket-client", "requests"] keywords = [ "jupyter", "firefly", @@ -25,7 +20,7 @@ keywords = [ "visualization", "images", "charts", - "tables" + "tables", ] classifiers = [ "Intended Audience :: Developers", @@ -35,7 +30,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Astronomy", "Topic :: Scientific/Engineering :: Visualization", "Programming Language :: Python", - "Programming Language :: Python :: 3" + "Programming Language :: Python :: 3", ] [project.urls] @@ -48,5 +43,215 @@ docs = [ "Sphinx~=7.1.0", "sphinx-automodapi", "pydata-sphinx-theme", - "myst-parser" + "myst-parser", +] +test = [ + "pytest>=8.3.4", + "pytest-container>=0.4.3", + "pytest-cov>=6.0.0", + "pytest-doctestplus>=1.4.0", + "pytest-mock>=3.14.0", + "pytest-xdist>=3.6.1", + "tox>=4.0", + "tox-uv>=1.20", +] +astropy = ["astropy>=6"] + +[tool.setuptools] +packages = ["firefly_client"] + +[tool.pytest.ini_options] +testpaths = ["firefly_client", "docs", "test"] +doctest_plus = "enabled" +text_file_format = "rst" +addopts = [ + "--doctest-rst", + "--numprocesses=4", + "--import-mode=importlib", + "--cov=firefly_client", + "--cov-append", + "--cov-report=term-missing", + "--cov-report=xml:coverage.xml", + "--cov-report=html:coverage", + "--doctest-modules", +] + +[tool.coverage.run] +omit = [ + "firefly_client/conftest.py", + "firefly_client/*setup_package*", + "firefly_client/tests/*", + "firefly_client/*/tests/*", + "firefly_client/extern/*", + "firefly_client/version*", + "*/firefly_client/conftest.py", + "*/firefly_client/*setup_package*", + "*/firefly_client/tests/*", + "*/firefly_client/*/tests/*", + "*/firefly_client/extern/*", + "*/firefly_client/version*", +] + +[tool.coverage.report] +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + # Don't complain about packages we have installed + "except ImportError", + # Don't complain if tests don't hit assertions + "raise AssertionError", + "raise NotImplementedError", + # Don't complain about script hooks + "def main(.*):", + # Ignore branches that don't pertain to this version of Python + "pragma: py{ignore_python_version}", + # Don't complain about IPython completion helper + "def _ipython_key_completions_", + # typing.TYPE_CHECKING is False at runtime + "if TYPE_CHECKING:", + # Ignore typing overloads + "@overload", +] + +[tool.tox] +env_list = ["clean", "3.10", "3.11", "3.12", "3.13", "3.14", "build_docs"] +requires = ["tox>=4.0", "tox-uv>=1.20"] + +[tool.tox.env_run_base] +commands = [ + [ + "pytest", + "--doctest-rst", + "--numprocesses=4", + "--import-mode=importlib", + "--cov=firefly_client", + "--cov-append", + "--cov-report=term-missing", + "--cov-report=xml:coverage.xml", + "--cov-report=html:coverage", + "--doctest-modules", + "{posargs}", + ], +] +runner = "uv-venv-lock-runner" +description = "run tests with the oldest supported version of key dependencies on {base_python}" +pass_env = ["TOXENV", "CI", "CC", "LOCALE_ARCHIVE", "LC_ALL"] +set_env = { MPLBACKEND = "agg" } +extras = ["test", "astropy"] +uv_python_preference = "only-managed" +allowlist_externals = ["python"] + +[tool.tox.env.clean] +description = "Clean artifacts before testing" +commands = [["coverage", "erase"]] +runner = "uv-venv-lock-runner" +extras = ["test", "astropy"] +uv_python_preference = "only-managed" +allowlist_externals = ["python"] + +[tool.tox.env.build_docs] +description = "invoke sphinx-build to build the HTML docs" +change_dir = "docs" +extras = ["docs"] +commands = [["make", "html"]] +uv_python_preference = "only-managed" +allowlist_externals = ["python", "make", "uv"] + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "docs", ] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Ensure code can be executed on python 3.10 +target-version = "py310" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F", "C90", "I", "RUF"] +ignore = ["RUF002"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = true + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + +[tool.ruff.lint.isort] +case-sensitive = true +combine-as-imports = true +force-wrap-aliases = true +known-local-folder = ["firefly_client"] +length-sort = true +lines-after-imports = 2 + +[tool.ruff.lint.mccabe] +# Flag errors (`C901`) whenever the complexity level exceeds 5. +max-complexity = 12 + +[tool.ruff.lint.pydoclint] +# Skip docstrings which fit on a single line. +ignore-one-line-docstrings = true + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/cb.ipynb b/test/cb.ipynb index 624af28..7c057d2 100644 --- a/test/cb.ipynb +++ b/test/cb.ipynb @@ -8,7 +8,9 @@ "source": [ "import firefly_client\n", "from firefly_client import FireflyClient\n", - "firefly_client.__dict__['__version__'] " + "\n", + "\n", + "firefly_client.__dict__[\"__version__\"]" ] }, { @@ -28,7 +30,9 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", + ")" ] }, { @@ -39,9 +43,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -52,26 +56,26 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", " if False:\n", " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", "\n", "\n", - " \n", "fc.add_listener(listener1)" ] }, diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..4af366a --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,9 @@ +from pytest_container import add_logging_level_options, set_logging_level_from_cli_args + + +def pytest_addoption(parser): + add_logging_level_options(parser) + + +def pytest_configure(config): + set_logging_level_from_cli_args(config) diff --git a/test/container.py b/test/container.py new file mode 100644 index 0000000..cde9a96 --- /dev/null +++ b/test/container.py @@ -0,0 +1,19 @@ +import random + +from pytest_container.inspect import PortForwarding, NetworkProtocol +from pytest_container.container import Container, EntrypointSelection + + +FIREFLY_CONTAINER = Container( + url="docker.io/ipac/firefly:latest", + extra_launch_args=["--memory=4g"], + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=random.randint(8000, 65534), + bind_ip="127.0.0.1", + ) + ], +) diff --git a/test/fc-version.py b/test/fc-version.py index 1ca4971..fc8ce32 100644 --- a/test/fc-version.py +++ b/test/fc-version.py @@ -1,4 +1,9 @@ -from firefly_client import FireflyClient import firefly_client -v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' -print('Version: %s' % v_str) + + +v_str = ( + firefly_client.__dict__["__version__"] + if "__version__" in firefly_client.__dict__ + else "development" +) +print("Version: %s" % v_str) diff --git a/test/firefly_slate_demo.py b/test/firefly_slate_demo.py new file mode 100644 index 0000000..5bfbe37 --- /dev/null +++ b/test/firefly_slate_demo.py @@ -0,0 +1,219 @@ +import astropy.utils.data + + +def download_from(url): + return astropy.utils.data.download_file(url, timeout=120, cache=True) + + +def load_moving_table(row, col, width, height, fc, cell_id=None): + moving_tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl" + ) + + r = fc.add_cell(row, col, width, height, "tables", cell_id) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } + tbl_name = fc.upload_file(moving_tbl_name) + + if r["success"]: + fc.show_table( + tbl_name, + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) + + +def add_simple_m31_image_table(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl" + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) + + +def add_simple_image_table(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl" + ) + + meta_info = {"datasource": "FITS"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) + + +def add_table_for_chart(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl" + ) + + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) + + +def load_image(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "images", cell_id) + + image_name = download_from( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + + if r["success"]: + fc.show_fits( + file_on_server=fc.upload_file(image_name), + viewer_id=r["cell_id"], + title="WISE Cutout", + ) + + +def load_image_metadata(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "tableImageMeta", cell_id) + + if r["success"]: + fc.show_image_metadata(viewer_id=r["cell_id"]) + + +def load_coverage_image(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "coverageImage", cell_id) + + if r["success"]: + fc.show_coverage(viewer_id=r["cell_id"]) + + +def load_moving(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "images", cell_id) + + if r["success"]: + v_id = r["cell_id"] + fc.show_fits( + plot_id="m49025b_143_2", + viewer_id=v_id, + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + viewer_id=v_id, + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + viewer_id=v_id, + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + viewer_id=v_id, + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) + + +def load_xy(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) + + if r["success"]: + trace1 = { + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(group_id=r["cell_id"], layout=layout_s, data=trace_data) + + +def load_histogram(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) + + if r["success"]: + histData = [ + { + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, + }, + } + ] + + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=r["cell_id"], layout=layout_hist, data=histData) + assert result is not None + + +def load_first_image_in_random(fc): + img_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits" + ) + + fc.show_fits(file_on_server=fc.upload_file(img_name)) + + +def load_second_image_in_random(fc): + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/test/test-5-instances-3-channels.py b/test/test-5-instances-3-channels.py index 407d973..bfe0228 100644 --- a/test/test-5-instances-3-channels.py +++ b/test/test-5-instances-3-channels.py @@ -2,31 +2,31 @@ def listener1(ev): - print('l1') + print("l1") print(ev) def listener2(ev): - print('l2') + print("l2") print(ev) def listener3(ev): - print('l3') + print("l3") print(ev) def listener4(ev): - print('l4') + print("l4") print(ev) -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' -local_host = 'http://127.0.0.1:8080/firefly' +lsst_demo_host = "https://lsst-demo.ncsa.illinois.edu/firefly" +local_host = "http://127.0.0.1:8080/firefly" host = local_host -channel1 = 'channel-test-1' -channel2 = 'channel-test-2' -channel3 = 'channel-test-3' +channel1 = "channel-test-1" +channel2 = "channel-test-2" +channel3 = "channel-test-3" print(""" @@ -42,19 +42,25 @@ def listener4(ev): """) fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True) -fc1_c1.add_extension(ext_type='POINT', title='a point') +fc1_c1.add_extension(ext_type="POINT", title="a point") # fc.add_extension(ext_type='LINE_SELECT', title='a line') -fc2_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=False) -fc3_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=False) +fc2_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False +) +fc3_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False +) fc1_c2 = FireflyClient.make_client(host, channel_override=channel2, launch_browser=True) -fc1_c2.add_extension(ext_type='POINT', title='a point') +fc1_c2.add_extension(ext_type="POINT", title="a point") fc1_c3 = FireflyClient.make_client(host, channel_override=channel3, launch_browser=True) -print(f'>>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}') -print(f'>>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}') -print(f'>>>>>> channel: {channel3}, firefly url: {fc1_c3.get_firefly_url()}') +print(f">>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}") +print(f">>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}") +print(f">>>>>> channel: {channel3}, firefly url: {fc1_c3.get_firefly_url()}") # adding stuff but there is not listener -fc1_c3.show_fits(url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits") -fc1_c3.add_extension(ext_type='POINT', title='a point no CB') +fc1_c3.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" +) +fc1_c3.add_extension(ext_type="POINT", title="a point no CB") # ------------ add listeners fc1_c1.add_listener(listener1) # one web socket should be made for first 3 (channel1) diff --git a/test/test-simple-callback-in-lab.ipynb b/test/test-simple-callback-in-lab.ipynb index fc87614..5224711 100644 --- a/test/test-simple-callback-in-lab.ipynb +++ b/test/test-simple-callback-in-lab.ipynb @@ -18,18 +18,20 @@ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", "\n", "# some host\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", - "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", + "irsa = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", + "fd = \"https://fireflydev.ipac.caltech.edu/firefly\"\n", "\n", - "#host = 'http://127.0.0.1:8080/suit'\n", + "# host = 'http://127.0.0.1:8080/suit'\n", "host = local_host\n", - "channel1 = 'channel-test-1'\n", + "channel1 = \"channel-test-1\"\n", "FireflyClient._debug = False\n", - "#fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", - "fc = FireflyClient.make_lab_client(start_browser_tab=False, start_tab=True, verbose=True )\n", - "#fc = FireflyClient.make_lab_client(start_tab=False)\n", + "# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", + "fc = FireflyClient.make_lab_client(\n", + " start_browser_tab=False, start_tab=True, verbose=True\n", + ")\n", + "# fc = FireflyClient.make_lab_client(start_tab=False)\n", "fc.get_firefly_url()" ] }, @@ -48,7 +50,10 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\",\n", + " plot_id=\"x2\",\n", + ")" ] }, { @@ -57,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.set_stretch('x1', stype='zscale')" + "fc.set_stretch(\"x1\", stype=\"zscale\")" ] }, { @@ -68,9 +73,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line', shortcut_key='meta-e')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\", shortcut_key=\"meta-e\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -81,26 +86,26 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", " if False:\n", " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", "\n", "\n", - " \n", "fc.add_listener(listener1)" ] }, @@ -111,6 +116,8 @@ "outputs": [], "source": [ "from firefly_client import __version__ as v\n", + "\n", + "\n", "v" ] }, diff --git a/test/test-simple-callback-response.py b/test/test-simple-callback-response.py index 7ba67ab..f1cf628 100644 --- a/test/test-simple-callback-response.py +++ b/test/test-simple-callback-response.py @@ -1,55 +1,63 @@ -from firefly_client import FireflyClient import firefly_client -import time +from firefly_client import FireflyClient + -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' -local_host = 'http://127.0.0.1:8080/firefly' -fd = 'https://fireflydev.ipac.caltech.edu/firefly' -irsa_host = 'https://irsa.ipac.caltech.edu/irsaviewer' -data_lsst_host = 'https://data.lsst.cloud/portal/app/' +lsst_demo_host = "https://lsst-demo.ncsa.illinois.edu/firefly" +local_host = "http://127.0.0.1:8080/firefly" +fd = "https://fireflydev.ipac.caltech.edu/firefly" +irsa_host = "https://irsa.ipac.caltech.edu/irsaviewer" +data_lsst_host = "https://data.lsst.cloud/portal/app/" # host = 'https://fireflydev.ipac.caltech.edu/firefly-multi-ticket/firefly/' -host = local_host -channel1 = 'channel-test-1' +host = local_host +channel1 = "channel-test-1" -v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' -print('Version: %s' % v_str) +v_str = ( + firefly_client.__dict__["__version__"] + if "__version__" in firefly_client.__dict__ + else "development" +) +print("Version: %s" % v_str) FireflyClient._debug = False token = None # fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) fc = FireflyClient.make_client(host, launch_browser=True, token=token) print(fc.get_firefly_url()) -fc.show_fits(url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits") +fc.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" +) def example_listener(ev): if False: print(ev) - if 'data' not in ev: - print('no data found in ev') + if "data" not in ev: + print("no data found in ev") return - data = ev['data'] - if 'payload' in data: - print(data['payload']) - if 'type' in data: - print(data['type']) - if data['type'] == 'POINT': - print(' plotId: ' + data['plotId']) - print(' image point: %s' % data['ipt']) - print(' world point: %s' % data['wpt']) - if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT': - print(' plotId: ' + data['plotId']) - print(' image points: %s to %s' % (data['ipt0'], data['ipt1'])) - print(' world points: %s to %s' % (data['wpt0'], data['wpt1'])) + data = ev["data"] + if "payload" in data: + print(data["payload"]) + if "type" in data: + print(data["type"]) + if data["type"] == "POINT": + print(" plotId: " + data["plotId"]) + print(" image point: %s" % data["ipt"]) + print(" world point: %s" % data["wpt"]) + if data["type"] == "LINE_SELECT" or data["type"] == "AREA_SELECT": + print(" plotId: " + data["plotId"]) + print(" image points: %s to %s" % (data["ipt0"], data["ipt1"])) + print(" world points: %s to %s" % (data["wpt0"], data["wpt1"])) -fc.add_extension(ext_type='POINT', title='Output Selected Point', shortcut_key='ctrl-p') -fc.add_extension(ext_type='LINE_SELECT', title='Output Selected line', shortcut_key='meta-b') -fc.add_extension(ext_type='AREA_SELECT', title='Output Selected Area', shortcut_key='a') +fc.add_extension(ext_type="POINT", title="Output Selected Point", shortcut_key="ctrl-p") +fc.add_extension( + ext_type="LINE_SELECT", title="Output Selected line", shortcut_key="meta-b" +) +fc.add_extension(ext_type="AREA_SELECT", title="Output Selected Area", shortcut_key="a") # ------------ add listener and wait fc.add_listener(example_listener) -print('listener is added') +print("listener is added") # time.sleep(3) # fc.remove_listener(example_listener) # time.sleep(2) # fc.add_listener(example_listener) -fc.wait_for_events() \ No newline at end of file +fc.wait_for_events() diff --git a/test/test-socket-not-added-until-listener.ipynb b/test/test-socket-not-added-until-listener.ipynb index 3a18baf..c7e981f 100644 --- a/test/test-socket-not-added-until-listener.ipynb +++ b/test/test-socket-not-added-until-listener.ipynb @@ -7,7 +7,7 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", - "#import astropy.utils.data" + "# import astropy.utils.data" ] }, { @@ -17,14 +17,14 @@ "outputs": [], "source": [ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", "host = local_host\n", - "channel1 = 'channel-test-1'\n", - "channel2 = 'channel-test-2'\n", - "channel3 = 'channel-test-3'\n", + "channel1 = \"channel-test-1\"\n", + "channel2 = \"channel-test-2\"\n", + "channel3 = \"channel-test-3\"\n", "\n", - "fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n" + "fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)" ] }, { @@ -44,8 +44,9 @@ } ], "source": [ - "\n", - "fc1_c1.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" + "fc1_c1.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", + ")" ] }, { @@ -67,9 +68,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc1_c1.add_extension(ext_type='LINE_SELECT', title='a line')\n", - "fc1_c1.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc1_c1.add_extension(ext_type='POINT', title='a point')" + "fc1_c1.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", + "fc1_c1.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc1_c1.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -89,10 +90,12 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", - " print('l1')\n", + " print(\"l1\")\n", " print(ev)\n", - " \n", + "\n", + "\n", "fc1_c1.add_listener(listener1)" ] }, diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py new file mode 100644 index 0000000..7286cc0 --- /dev/null +++ b/test/test_5_instances_3_channels.py @@ -0,0 +1,90 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_5_instances_3_channels(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + mocker.patch("webbrowser.open") + + def listener1(ev): + print("l1") + print(ev) + + def listener2(ev): + print("l2") + print(ev) + + def listener3(ev): + print("l3") + print(ev) + + def listener4(ev): + print("l4") + print(ev) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + channel1 = "channel-test-1" + channel2 = "channel-test-2" + channel3 = "channel-test-3" + + print( + """ + -------------------------------------------- + 5 instance of firefly that define 3 channels") + - since listeners are only added on 2 channels, only two websockets are made") + - The first 3 instances use the same channel so they share a websocket") + - instance 5 never adds a listener and therefore not websocket is made") + - The initial response on the listeners will show the number connections") + - example look for something like: channel-test: ['1b4', '1b5']") + - You can see this on the server side my monitoring firefly.log") + -------------------------------------------- + """ + ) + + fc1_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc1_c1.add_extension(ext_type="POINT", title="a point") + # fc.add_extension(ext_type='LINE_SELECT', title='a line') + fc2_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc3_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc1_c2 = FireflyClient.make_client( + host, channel_override=channel2, launch_browser=False + ) + fc1_c2.add_extension(ext_type="POINT", title="a point") + fc1_c3 = FireflyClient.make_client( + host, channel_override=channel3, launch_browser=False + ) + print(f">>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}") + print(f">>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}") + print(f">>>>>> channel: {channel3}, firefly url: {fc1_c3.get_firefly_url()}") + # adding stuff but there is not listener + fc1_c3.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + fc1_c3.add_extension(ext_type="POINT", title="a point no CB") + + # ------------ add listeners + fc1_c1.add_listener( + listener1 + ) # one web socket should be made for first 3 (channel1) + fc2_c1.add_listener(listener2) + fc3_c1.add_listener(listener3) + fc1_c2.add_listener(listener4) # one web socket should be made for channel2 diff --git a/test/test_basic_demo_tableload.py b/test/test_basic_demo_tableload.py new file mode 100644 index 0000000..a964be9 --- /dev/null +++ b/test/test_basic_demo_tableload.py @@ -0,0 +1,48 @@ +import os +import time +from urllib import request +from pathlib import Path + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_basic_tableload(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + test_data_path = Path(os.curdir) + + # This was originally /hydra/cm/MF.20210502.18830.fits + localfile = os.path.join(test_data_path, "wise-00.fits") + + request.urlretrieve( + "http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits", + localfile, + ) + + filename = fc.upload_file(localfile) + fc.show_table(filename) + fc.show_table(filename, table_index=2) + + # localfile = os.path.join(testdata_repo_path, "Mr31objsearch.xml") + # filename = fc.upload_file(localfile) + + # fc.show_table(filename, tbl_id="votable-0") + # fc.show_table(filename, tbl_id="votable-1", table_index=1) + + os.remove(localfile) diff --git a/test/test_demo_3color.py b/test/test_demo_3color.py new file mode 100644 index 0000000..6454e5c --- /dev/null +++ b/test/test_demo_3color.py @@ -0,0 +1,73 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_3color(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + target = "210.80227;54.34895;EQ_J2000" # Galaxy M101 + viewer_id = "3C" + r = fc.add_cell(0, 0, 1, 1, "images", viewer_id) + rv = "92,-2,92,8,NaN,2,44,25,600,120" + if r["success"]: + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + ] + + fc.show_fits_3color(threeC, plot_id="wise_m101", viewer_id=viewer_id) + # Set stretch using hue-preserving algorithm with scaling coefficients .15 for RED, 1.0 for GREEN, and .4 for BLUE. + fc.set_stretch_hprgb( + plot_id="wise_m101", asinh_q_value=4.2, scaling_k=[0.15, 1, 0.4] + ) + # Set per-band stretch + fc.set_stretch(plot_id="wise_m101", stype="ztype", algorithm="asinh", band="ALL") + # Set contrast and bias for each band + fc.set_rgb_colors(plot_id="wise_m101", bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2]) + # Set to use red and blue band only, with default bias and contrast + fc.set_rgb_colors(plot_id="wise_m101", use_green=False) diff --git a/test/test_demo_HiPS.py b/test/test_demo_HiPS.py new file mode 100644 index 0000000..f24ff6f --- /dev/null +++ b/test/test_demo_HiPS.py @@ -0,0 +1,53 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_hi_ps(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + mocker.patch("webbrowser.open") + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + target = "148.892;69.0654;EQ_J2000" + + viewer_id = "hipsDIV1" + r = fc.add_cell(0, 0, 4, 2, "images", viewer_id) + if r["success"]: + hips_url = "http://alasky.u-strasbg.fr/DSS/DSSColor" + status = fc.show_hips( + viewer_id=viewer_id, + plot_id="aHipsID1-1", + hips_root_url=hips_url, + Title="HiPS-DSS", + WorldPt=target, + ) + + fc.set_color("aHipsID1-1", colormap_id=6, bias=0.6, contrast=1.5) + + hips_url = "http://alasky.u-strasbg.fr/DSS/DSS2Merged" + status = fc.show_hips( + plot_id="aHipsID1-2", hips_root_url=hips_url, Title="HiPS-DSS2", WorldPt=target + ) + + assert status is not None + + hips_url = "http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1" + status = fc.show_hips(plot_id="aHipsID2", hips_root_url=hips_url) + + assert status is not None diff --git a/test/test_demo_advanced_all.py b/test/test_demo_advanced_all.py new file mode 100644 index 0000000..fa59993 --- /dev/null +++ b/test/test_demo_advanced_all.py @@ -0,0 +1,275 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_adv(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + mocker.patch("webbrowser.open") + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov) + + target = "10.68479;41.26906;EQ_J2000" # Galaxy M31 + # other notable targets to try + # target = '148.88822;69.06529;EQ_J2000' #Galaxy M81 + # target = '202.48417;47.23056;EQ_J2000' #Galaxy M51 + # target = '136.9316774;+1.1195886;galactic' # W5 star-forming region + + r = fc.add_cell(0, 4, 2, 2, "tables", "main") + + if r["success"]: + fc.show_table( + tbl_id="wiseCatTbl", + title="WISE catalog", + target_search_info={ + "catalogProject": "WISE", + "catalog": "allwise_p3as_psd", + "position": target, + "SearchMethod": "Cone", + "radius": 1200, + }, + options={"removable": True, "showUnits": False, "showFilters": True}, + ) + + # Add first chart - scatter (plot.ly direct plot) + # in cell 0, 0, 2, 2, + viewer_id = "newChartContainer" + r = fc.add_cell(0, 0, 2, 2, "xyPlots", viewer_id) + + if r["success"]: + trace1 = { + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro-w2mpro", + "y": "tables::w2mpro-w3mpro", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Color-Color", + "xaxis": {"title": "w1mpro-w2mpro (mag)"}, + "yaxis": {"title": "w2mpro-w3mpro (mag)"}, + } + fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data) + + # Add second chart - heatmap (plot.ly direct plot) + # in cell 2, 0, 2, 3 + viewer_id = "heatMapContainer" + r = fc.add_cell(2, 0, 2, 3, "xyPlots", viewer_id) + print(r) + + if r["success"]: + dataHM = [ + { + "type": "fireflyHeatmap", + "name": "w1 vs. w2", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w2mpro", + "colorscale": "Blues", + }, + { + "type": "fireflyHeatmap", + "name": "w1 vs. w3", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w3mpro", + "colorscale": "Reds", + "reversescale": True, + }, + { + "type": "fireflyHeatmap", + "name": "w1 vs. w4", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w4mpro", + "colorscale": "Greens", + }, + ] + + layout_hm = { + "title": "Magnitude-magnitude densities", + "xaxis": {"title": "w1 photometry (mag)"}, + "yaxis": {"title": ""}, + "firefly": {"xaxis": {"min": 5, "max": 20}, "yaxis": {"min": 4, "max": 18}}, + } + + fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM) + + # Add third chart - histogram (plot.ly direct plot) + # in cell 2, 2, 2, 3 + viewer_id = "histContainer" + r = fc.add_cell(2, 2, 2, 3, "xyPlots", viewer_id) + + if r["success"]: + dataH = [ + { + "type": "fireflyHistogram", + "name": "w1mpro", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "wiseCatTbl", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "w1mpro", + }, + }, + }, + { + "type": "fireflyHistogram", + "name": "w2mpro", + "marker": {"color": "rgba(102, 153, 0, 0.7)"}, + "firefly": { + "tbl_id": "wiseCatTbl", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 40, + "columnOrExpr": "w2mpro", + }, + }, + }, + ] + + layout_hist = { + "title": "Photometry histogram", + "xaxis": {"title": "photometry (mag)"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH) + + assert result is not None + + # Add fourth chart - 3D plot (plot.ly direct plot) + # in cell 2, 4, 2, 3, + viewer_id = "3dChartContainer" + r = fc.add_cell(2, 4, 2, 3, "xyPlots", viewer_id) + + if r["success"]: + data3d = [ + { + "type": "scatter3d", + "name": "w1-w2-w3", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w2mpro", + "z": "tables::w3mpro", + "mode": "markers", + "marker": { + "size": 3, + "color": "rgba(127, 127, 127, 1)", + "line": {"color": "rgba(127, 127, 127, 0.5)", "width": 1}, + }, + "hoverinfo": "x+y+z", + } + ] + + fsize = {"size": 11} + layout3d = { + "title": "Photometry in band 1, 2, 3", + "scene": { + "xaxis": {"title": "w1 (mag)", "titlefont": fsize}, + "yaxis": {"title": "w2 (mag)", "titlefont": fsize}, + "zaxis": {"title": "w3 (mag)", "titlefont": fsize}, + }, + } + + fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d) + + # Add a three color fits + # in cell 0, 2, 2, 2 + viewer_id = "3C" + r = fc.add_cell(0, 2, 2, 2, "images", viewer_id) + rv = "92,-2,92,8,NaN,2,44,25,600,120" + if r["success"]: + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + ] + fc.show_fits_3color(threeC, viewer_id=viewer_id) + + fc.change_triview_layout(FireflyClient.BIVIEW_IChCov_T) + + rv = "92,-2,92,8,NaN,2,44,25,600,120" + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + ] + fc.show_fits_3color(threeC) diff --git a/test/test_demo_advanced_steps.py b/test/test_demo_advanced_steps.py new file mode 100644 index 0000000..5cca559 --- /dev/null +++ b/test/test_demo_advanced_steps.py @@ -0,0 +1,228 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_adv_steps(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov) + + # ## Display tables, images, XY charts, and Histograms in Window/Grid like layout + # + # Each rendered unit on Firefly Slate Viewer is called a'cell'. To render a cell and its content, the cell location (row, column, width, height), element type and cell ID are needed. (row, column) and (width, height) represent the position and size of the cell in terms of the grid blocks on Firefly Slate Viewer. Element types include types of 'tables', images', 'xyPlots', 'tableImageMeta' and 'coverageImage'. + + # We use astropy here for convenience, but firefly_client itself does not depend on astropy. + + # + # ## Display tables and catalogs + # + # + # Add some tables into cell 'main' (default cell id for tables) + # + # + # - add first table in cell 'main': + # - 'main' is the cell id currently supported by Firefly for element type 'tables' + # - this cell is shown at row = 0, col = 0 with width = 4, height = 2 + + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl", + timeout=120, + cache=True, + ) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) + + # - add 2nd table in cell 'main' for chart and histogram + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", + timeout=120, + cache=True, + ) + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) + + # - add 3rd table in cell 'main' + tbl_name = download_file( + "http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl", + timeout=120, + cache=True, + ) + meta_info = {"datasource": "DP"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) + + # - add 4th table in cell 'main' + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl", + timeout=120, + cache=True, + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) + + # ## Add different types of image displays + # + # - show cell with id 'image-meta' containaing image from the active table containing datasource column in cell 'main' + # - the cell is shown at row = 2, col = 0 with width = 4, height = 2 + # - show cell 'wise-content' containing fits at row = 0, col = 4 with width = 2, height = 2 + image_name = download_file( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + fc.show_fits(file_on_server=fc.upload_file(image_name), title="WISE Cutout") + + # - show 4 fits of moving objects in cell 'movingStff' at row = 2, col = 4 with width = 2, height = 2 + fc.show_fits( + plot_id="m49025b_143_2", + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) + + # ## Add charts (xy plot and histogram) + # + # - show the cell 'chart-cell-xy' with xy plot related to the table with id 'tbl_chart' in cell 'main' + # - this cell is shown at row = 4, col = 0 with width = 2, height = 3 + trace1 = { + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(layout=layout_s, data=trace_data) + + # - show the cell with histogram related to the table with id 'tbl_chart' in cell 'main', + # - this cell is shown at row = 4, col = 2, with width = 2, height = 3 + # - the cell id is automatically created by FireflyClient in case it is not specified externally. + histData = [ + { + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, + }, + } + ] + + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(layout=layout_hist, data=histData) + assert result is not None + + # ## Add more images + # + # - show coverage image associated with the active table in cell 'main' + # - this cell is shown at row = 4, col = 4 with width = 2, height = 3 + # - show image in random location without passing cell location and cell id (i.e. without calling add_cell) + # - the image is shown in the cell with id 'DEFAULT_FITS_VIEWER_ID' in default, + # - and the cell is automatically located by Firefly + img_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits", + timeout=120, + cache=True, + ) + fc.show_fits(file_on_server=fc.upload_file(img_name)) + fc.show_fits(plot_id="zzz", file_on_server=fc.upload_file(img_name)) + + # - show second image in random. it is shown in the same cell as the previous one + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/test/test_demo_advanced_table_images.py b/test/test_demo_advanced_table_images.py new file mode 100644 index 0000000..e0efe8b --- /dev/null +++ b/test/test_demo_advanced_table_images.py @@ -0,0 +1,57 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_adv_table_imgs(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + # ## Display tables and catalogs + + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # Add some tables into cell 'main' (default grid viewer id for tables) + # first table in cell 'main' + fs.load_moving_table(0, 0, 4, 2, fc) + + # add table in cell 'main' for chart and histogram + fs.add_table_for_chart(fc) + # add table in cell 'main' + fs.add_simple_image_table(fc) + # add table in cell 'main' + fs.add_simple_m31_image_table(fc) + # ## Add different types of image displays + # show cell containing the image from the active table with datasource column in cell 'main' + fs.load_image_metadata(2, 0, 4, 2, fc, "image-meta") + # show cell containing FITS in cell 'wise-cutout' + fs.load_image(0, 4, 2, 2, fc, "wise-cutout") # load an image + # show cell with 4 FITS of moving objects in cell 'movingStff' + fs.load_moving(2, 4, 2, 2, fc, "movingStuff") + # ## Add charts (xy plot and histogram) + # show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main' + fs.load_xy(4, 0, 2, 3, fc, "chart-cell-xy") + # show histogram associated with the table for chart in cell 'main', the cell id is generated by firefly_client + fs.load_histogram(4, 2, 2, 3, fc) + # ## Add more images + # show cell containing coverage image associated with the active table in cell 'main' + fs.load_coverage_image(4, 4, 3, 3, fc, "image-coverage") + # show cell containing image in ranmon location without passing the location and cell id + fs.load_first_image_in_random(fc) + # show second image in random location. This image is located in the same cell as the previous one + fs.load_second_image_in_random(fc) diff --git a/test/test_demo_advanced_tables_images_upload.py b/test/test_demo_advanced_tables_images_upload.py new file mode 100644 index 0000000..f204678 --- /dev/null +++ b/test/test_demo_advanced_tables_images_upload.py @@ -0,0 +1,64 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_adv_table_imgs_upload(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + mocker.patch("webbrowser.open") + fc = FireflyClient.make_client(url=host, launch_browser=False) + + # ## Display tables, images, XY charts, and Histograms in Window/Grid like layout + # + # Each rendered unit on Firefly. + # Please refer to firefly_slate_demo.py which contains functions to render cells with element tables, images, xy charts or histograms onto Firefly Slate Viewer by using firefly_client API. + # ## Display tables and catalogs + # Open a browser to the firefly server in a new tab. Only works when running the notebook locally. + # Add some tables into cell 'main' (default grid viewer id for tables) + # first table in cell 'main' + fs.load_moving_table(0, 0, 4, 2, fc) + # add table in cell 'main' for chart and histogram + fs.add_table_for_chart(fc) + # add table in cell 'main' + fs.add_simple_image_table(fc) + # add table in cell 'main' + fs.add_simple_m31_image_table(fc) + f = "/Users/roby/fits/2mass-m31-2412rows.tbl" # FIX: This needs to be either a URL download or a file included in the repo + file = fc.upload_file(f) + fc.show_table(file) + fc.show_table("${cache-dir}/upload_2482826742890803252.fits") + # ## Add different types of image displays + # show cell containing the image from the active table with datasource column in cell 'main' + fs.load_image_metadata(2, 0, 4, 2, fc, "image-meta") + # show cell containing FITS in cell 'wise-cutout' + fs.load_image(0, 4, 2, 2, fc, "wise-cutout") # load an image + # show cell with 4 FITS of moving objects in cell 'movingStff' + fs.load_moving(2, 4, 2, 2, fc, "movingStuff") + # ## Add charts (xy plot and histogram) + # show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main' + fs.load_xy(4, 0, 2, 3, fc, "chart-cell-xy") + # show histogram associated with the table for chart in cell 'main', the cell id is generated by firefly_client + fs.load_histogram(4, 2, 2, 3, fc) + # ## Add more images + # show cell containing coverage image associated with the active table in cell 'main' + fs.load_coverage_image(4, 4, 3, 3, fc, "image-coverage") + # show cell containing image in ranmon location without passing the location and cell id + fs.load_first_image_in_random(fc) + # show second image in random location. This image is located in the same cell as the previous one + fs.load_second_image_in_random(fc) diff --git a/test/test_demo_basic.py b/test/test_demo_basic.py new file mode 100644 index 0000000..9e472ab --- /dev/null +++ b/test/test_demo_basic.py @@ -0,0 +1,91 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_basics(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + # ## Download Sample Data + # For the following example, we download a sample image and table by using astropy (it's used only for convenience, firefly_client itself does not depend on astropy): + # Download a cutout of a WISE band as 1 FITS image: + image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + filename = download_file(image_url, cache=True, timeout=120) + # Download a 2MASS catalog using an IRSA VO Table Access Protocol ([TAP](https://irsa.ipac.caltech.edu/docs/program_interface/astropy_TAP.html)) search: + table_url = ( + "http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&" + + "QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec)," + + "CIRCLE('J2000',70.0,20.0,0.1))=1" + ) + tablename = download_file(table_url, timeout=120, cache=True) + # ## Display Tables and Catalogs + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # localbrowser, browser_url = fc.launch_browser() + # If it does not open automatically, try using the following command to display a web browser link to click on: + fc.display_url() + # Alternatively, uncomment the lines below (while commenting out the 2 lines above) to display the browser application in the notebook: + # from IPython.display import IFrame + # print('url: %s' % fc.get_firefly_url()) + # IFrame(fc.get_firefly_url(), 1100, 1000) + # The current example involves 2 images in FITS format to be viewed through the chosen browser, both in different ways. + # For the 1st method, upload a local file to the server and then display it: + imval = fc.upload_file(filename) + status = fc.show_fits( + file_on_server=imval, plot_id="wise-cutout", title="WISE Cutout" + ) + assert status is not None + # For the 2nd method, pull an image directly from a URL: + status = fc.show_fits( + file_on_server=None, + plot_id="wise-fullimage", + URL="http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits", + ) + # Upload a catalog table (with coordinates) to the server so it will overlay the image with markers by default. Note that markers can be visible on both "wise-cutout" and "wise-fullimage" since they would include the same field of view: + file = fc.upload_file(tablename) + status = fc.show_table( + file, tbl_id="tablemass", title="My 2MASS Catalog", page_size=50 + ) + # Add an xy plot using the uploaded table data: + status = fc.show_xyplot(tbl_id="tablemass", xCol="j_m", yCol="h_m-k_m") + # Alternatively, more generic method show_chart() can be used to create the same plot + # trace0 = {'tbl_id': 'tablemass', 'x': "tables::j_m", 'y': "tables::h_m-k_m", + # 'type' : 'scatter', 'mode': 'markers'} + # status = fc.show_chart(data=[trace0]) + # Zoom into the full image by a factor of 2 (note the `plot_id` parameter we used earlier) + status = fc.set_zoom("wise-fullimage", 2) + # Pan the full image to center on a given celestial coordinate: + status = fc.set_pan("wise-fullimage", x=70, y=20, coord="J2000") + # Set the stretch for the full image based on IRAF zscale interval with a linear algorithm: + status = fc.set_stretch("wise-fullimage", stype="zscale", algorithm="linear") + # Change color of the image: + status = fc.set_color("wise-fullimage", colormap_id=6, bias=0.6, contrast=1.5) + # Add region data to the cutout image (2 areas to begin with): + my_regions = [ + "image;polygon 125 25 160 195 150 150 #color=cyan", + "icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}", + ] + status = fc.add_region_data( + region_data=my_regions, region_layer_id="layer1", plot_id="wise-cutout" + ) + # Remove the second region while keeping the first one visible: + fc.remove_region_data(region_data=my_regions[1], region_layer_id="layer1") diff --git a/test/test_demo_lsst_footprint.py b/test/test_demo_lsst_footprint.py new file mode 100644 index 0000000..c62f509 --- /dev/null +++ b/test/test_demo_lsst_footprint.py @@ -0,0 +1,57 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_lsst_footprint(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client( + url=host, launch_browser=False + ) # ## Start with Image Data + + # The data used in this example are taken from http://web.ipac.caltech.edu/staff/shupe/firefly_testdata and can be downloaded. Download a file using the image URL below and upload it to the server: + image_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits" + filename = download_file(image_url, cache=True, timeout=120) + imval = fc.upload_file(filename) + + # Set the `plot_id` for the image and display it through the opened browser: + plotid = "footprinttest" + status = fc.show_fits( + file_on_server=imval, plot_id=plotid, title="footprints HSC R-band" + ) + + # ## Add Data for Footprints + + # Upload a table with footprint data using the following url from where it can be downloaded: + table_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml" + footprint_table = download_file(table_url, cache=True, timeout=120) + tableval = fc.upload_file(footprint_table) + + # Overlay a footprint layer (while comparing a highlighted section to the overall footprint) onto the plot using the above table: + status = fc.overlay_footprints( + tableval, + title="footprints HSC R-band", + footprint_layer_id="footprint_layer_1", + plot_id=plotid, + highlightColor="yellow", + selectColor="cyan", + style="fill", + color="rgba(74,144,226,0.30)", + ) + assert status is not None diff --git a/test/test_demo_region.py b/test/test_demo_region.py new file mode 100644 index 0000000..a517c41 --- /dev/null +++ b/test/test_demo_region.py @@ -0,0 +1,64 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_region(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + # Download a sample image and table. + # + # We use astropy here for convenience, but firefly_client itself does not depend on astropy. + + # Download a cutout of a WISE band as 1 FITS image: + image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + filename = download_file(image_url, cache=True, timeout=120) + + # ## Display tables and catalogs + + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # localbrowser, browser_url = fc.launch_browser() + + # If it does not open automatically, try using the following command to display a web browser link to click on: + fc.display_url() + + # Upload a local file to the server and then display it (while keeping in mind the `plot_id` parameter being chosen): + imval = fc.upload_file(filename) + status = fc.show_fits( + file_on_server=imval, plot_id="region test", title="text region test" + ) + + # Add region data containing text region with 'textangle' property + text_regions = [ + 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font="Times 16 bold normal" textangle=30', + 'image;text 100 25 # color=pink text={text angle is -20 deg } font="Times 16 bold italic" textangle=-20', + "circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan", + "image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}", + "circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and ' in it}", + ] + status = fc.add_region_data( + region_data=text_regions, + region_layer_id="layerTextRegion", + plot_id="region test", + ) + assert status is not None diff --git a/test/test_demo_show-image.py b/test/test_demo_show-image.py new file mode 100644 index 0000000..4657546 --- /dev/null +++ b/test/test_demo_show-image.py @@ -0,0 +1,24 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_show_img(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + fc.show_fits(URL="http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits") diff --git a/test/test_fc_version.py b/test/test_fc_version.py new file mode 100644 index 0000000..42c1e55 --- /dev/null +++ b/test/test_fc_version.py @@ -0,0 +1,12 @@ +import firefly_client + + +def test_version(): + v_str = ( + firefly_client.__dict__["__version__"] + if "__version__" in firefly_client.__dict__ + else "development" + ) + print("Version: %s" % v_str) + assert v_str is not None + assert isinstance(v_str, str) diff --git a/test/test_plot_interface.py b/test/test_plot_interface.py new file mode 100644 index 0000000..1a413a6 --- /dev/null +++ b/test/test_plot_interface.py @@ -0,0 +1,119 @@ +import time + +import pytest +from astropy.io import fits +from pytest_mock import MockerFixture +from astropy.table import Table +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import ( + FireflyClient, + plot as ffplt, +) + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_plt(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + ffplt.use_client(fc) + + # The next cell will attempt to launch a browser, if there is not already a web page connected to the instance of `FireflyClient`. + ffplt.open_browser() + + # If the browser launch is unsuccessful, a link will be displayed for your web browser. + + # The next cell will display a clickable link + ffplt.display_url() + + # #### Optional: specifying a server and channel. + # + # By default, a channel is generated for you. If you find it more convenient to use a specific server and channel, uncomment out the next cell to generate a channel from your username. + # ## Restart here + + # If needed, clear the viewer and reset the layout to the defaults. + ffplt.clear() + ffplt.reset_layout() + + # ### Upload a table directly from disk. + # For the case of a file already on disk, pass the path and a title for the table. Here we download a file. + m31_table_fname = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl", + timeout=120, + cache=True, + ) + + # Upload the table file from disk, and enable the coverage image + tbl_id = ffplt.upload_table(m31_table_fname, title="M31 2MASS", view_coverage=True) + assert tbl_id is not None + + # Make a histogram from the last uploaded table, with the minimum required parameters + ffplt.hist("j_m") + + # Make a scatter plot from the last uploaded table, with the minimum required parameters + ffplt.scatter("h_m - k_m", "j_m") + + # ### Viewing a Python image object + + # Load a FITS file using astropy.io.fits + + m31_image_fname = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits", + timeout=120, + cache=True, + ) + hdu = fits.open(m31_image_fname) + type(hdu) + + # Upload to the viewer. The return value is the `plot_id`. + + # In[13]: + + ffplt.upload_image(hdu) + + # ### Viewing a Numpy array + + # If the `astropy` package is installed, a Numpy array can be uploaded. + + # In[14]: + + data = hdu[0].data + type(data) + + # Upload to the viewer. The return value is the `plot_id`. + + # In[15]: + + ffplt.upload_image(data) + + # ### Uploading a Python table object + + # Load a table using astropy + + # In[16]: + + wise_tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", + timeout=120, + cache=True, + ) + + # Read the downloaded table in IPAC table format + + wise_table = Table.read(wise_tbl_name, format="ipac") + + # Upload to the viewer with a set title + + ffplt.upload_table(wise_table, title="WISE Demo Table") diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py new file mode 100644 index 0000000..43f48ae --- /dev/null +++ b/test/test_simple_callback.py @@ -0,0 +1,29 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_simple_callback(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + channel1 = "channel-test-1" + FireflyClient._debug = False + fc = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + # fc = FireflyClient.make_lab_client(start_tab=False) + firefly_url = fc.get_firefly_url() + assert host in firefly_url diff --git a/test/test_simple_callback_in_lab.ipynb b/test/test_simple_callback_in_lab.ipynb new file mode 100644 index 0000000..5224711 --- /dev/null +++ b/test/test_simple_callback_in_lab.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", + "\n", + "# some host\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", + "irsa = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", + "fd = \"https://fireflydev.ipac.caltech.edu/firefly\"\n", + "\n", + "# host = 'http://127.0.0.1:8080/suit'\n", + "host = local_host\n", + "channel1 = \"channel-test-1\"\n", + "FireflyClient._debug = False\n", + "# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", + "fc = FireflyClient.make_lab_client(\n", + " start_browser_tab=False, start_tab=True, verbose=True\n", + ")\n", + "# fc = FireflyClient.make_lab_client(start_tab=False)\n", + "fc.get_firefly_url()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.get_firefly_url()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\",\n", + " plot_id=\"x2\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.set_stretch(\"x1\", stype=\"zscale\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Extensions can be made but there is not web socket connections until a listener is added\n", + "\n", + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\", shortcut_key=\"meta-e\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# A Web socket should not be made until this cell is added\n", + "\n", + "\n", + "def listener1(ev):\n", + " if False:\n", + " print(ev)\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", + " return\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", + "\n", + "\n", + "fc.add_listener(listener1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import __version__ as v\n", + "\n", + "\n", + "v" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py new file mode 100644 index 0000000..cb66be9 --- /dev/null +++ b/test/test_simple_callback_response.py @@ -0,0 +1,66 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_simple_callback_response(container: ContainerData, mocker: MockerFixture): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + FireflyClient._debug = False + token = None + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + # fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) + fc = FireflyClient.make_client(host, launch_browser=False, token=token) + print(fc.get_firefly_url()) + fc.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + + def example_listener(ev): + if False: + print(ev) + if "data" not in ev: + print("no data found in ev") + return + data = ev["data"] + if "payload" in data: + print(data["payload"]) + if "type" in data: + print(data["type"]) + if data["type"] == "POINT": + print(" plotId: " + data["plotId"]) + print(" image point: %s" % data["ipt"]) + print(" world point: %s" % data["wpt"]) + if data["type"] == "LINE_SELECT" or data["type"] == "AREA_SELECT": + print(" plotId: " + data["plotId"]) + print(" image points: %s to %s" % (data["ipt0"], data["ipt1"])) + print(" world points: %s to %s" % (data["wpt0"], data["wpt1"])) + + fc.add_extension( + ext_type="POINT", title="Output Selected Point", shortcut_key="ctrl-p" + ) + fc.add_extension( + ext_type="LINE_SELECT", title="Output Selected line", shortcut_key="meta-b" + ) + fc.add_extension( + ext_type="AREA_SELECT", title="Output Selected Area", shortcut_key="a" + ) + # ------------ add listener and wait + fc.add_listener(example_listener) + print("listener is added") + # time.sleep(3) + # fc.remove_listener(example_listener) + # time.sleep(2) + # fc.add_listener(example_listener) diff --git a/test/test_socket_not_added_until_listener.ipynb b/test/test_socket_not_added_until_listener.ipynb new file mode 100644 index 0000000..561ef4c --- /dev/null +++ b/test/test_socket_not_added_until_listener.ipynb @@ -0,0 +1,151 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient\n", + "# import astropy.utils.data" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", + "host = local_host\n", + "channel1 = \"channel-test-1\"\n", + "channel2 = \"channel-test-2\"\n", + "channel3 = \"channel-test-3\"\n", + "\n", + "fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fc1_c1.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'success': True}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Extensions can be made but there is not web socket connections until a listener is added\n", + "\n", + "fc1_c1.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", + "fc1_c1.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc1_c1.add_extension(ext_type=\"POINT\", title=\"a point\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "l1\n", + "{'data': {'payload': {'channel-test-1': ['1e', '2b', '2d']}, 'type': 'app_data.wsConnUpdated'}, 'scope': 'SELF', 'dataType': 'JSON', 'name': 'FLUX_ACTION', 'from': '-1'}\n" + ] + } + ], + "source": [ + "# A Web socket should not be made until this cell is added\n", + "\n", + "\n", + "def listener1(ev):\n", + " print(\"l1\")\n", + " print(ev)\n", + "\n", + "\n", + "fc1_c1.add_listener(listener1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# does not close the web socket, since it might be shared\n", + "fc1_c1.remove_listener(listener1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# this will actually disconnect the web socket, any other client in this python instance will lose their connection as well\n", + "fc1_c1.disconnect()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py new file mode 100644 index 0000000..14742c3 --- /dev/null +++ b/test/test_socket_not_added_until_listener.py @@ -0,0 +1,46 @@ +import time + +import pytest +from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + +from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_socket_not_added_until_listener( + container: ContainerData, mocker: MockerFixture +): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + channel1 = "channel-test-1" + + fc1_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc1_c1.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + + fc1_c1.add_extension(ext_type="LINE_SELECT", title="a line") + fc1_c1.add_extension(ext_type="AREA_SELECT", title="a area") + fc1_c1.add_extension(ext_type="POINT", title="a point") + + def listener1(ev): + print("l1") + print(ev) + + fc1_c1.add_listener(listener1) + time.sleep(1) + fc1_c1.remove_listener(listener1) + time.sleep(1) + fc1_c1.disconnect() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..954b38f --- /dev/null +++ b/uv.lock @@ -0,0 +1,1114 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903 }, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "astropy" +version = "6.1.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "astropy-iers-data", marker = "python_full_version < '3.11'" }, + { name = "numpy", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pyerfa", marker = "python_full_version < '3.11'" }, + { name = "pyyaml", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/f8/9c6675ab4c646b95aae2762d108f6be4504033d91bd50da21daa62cab5ce/astropy-6.1.7.tar.gz", hash = "sha256:a405ac186306b6cb152e6df2f7444ab8bd764e4127d7519da1b3ae4dd65357ef", size = 7063411 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/4f/27f91eb9cdaa37835e52496dcad00fd89969ef5154795697987d031d0605/astropy-6.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be954c5f7707a089609053665aeb76493b79e5c4753c39486761bc6d137bf040", size = 6531137 }, + { url = "https://files.pythonhosted.org/packages/4b/f2/fb2c6c1d31c21df0d4409ecd5e9788795be6f8f80b67008c8191488d55cf/astropy-6.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b5e48df5ab2e3e521e82a7233a4b1159d071e64e6cbb76c45415dc68d3b97af1", size = 6410376 }, + { url = "https://files.pythonhosted.org/packages/fd/68/65ad3ea77440df2e8625d8fee585d5fc6049f33a61e49221f91d8de0e3df/astropy-6.1.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55c78252633c644361e2f7092d71f80ef9c2e6649f08d97711d9f19af514aedc", size = 9892774 }, + { url = "https://files.pythonhosted.org/packages/b4/41/e366fc5baff41f7b433f07a46c053a24459e93d2912690d099f0eefabfc3/astropy-6.1.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985e5e74489d23f1a11953b6b283fccde3f46cb6c68fee4f7228e5f6d8350ba9", size = 9962419 }, + { url = "https://files.pythonhosted.org/packages/1e/a0/e6c1ef80f7e20fb600b3af742d227e6356704dbda3763ff1d76a53a0fd7b/astropy-6.1.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc2ea28ed41a3d92c39b1481d9c5be016ae58d68f144f3fd8cecffe503525bab", size = 9987760 }, + { url = "https://files.pythonhosted.org/packages/49/93/6b23e75d690763a9d702038c74ea9a74181a278fe362fbeecea35b691e4a/astropy-6.1.7-cp310-cp310-win32.whl", hash = "sha256:4e4badadd8dfa5dca08fd86e9a50a3a91af321975859f5941579e6b7ce9ba199", size = 6274137 }, + { url = "https://files.pythonhosted.org/packages/6e/e1/af92dc2132547e3998476a4b0ab19d15c50d8ec1d85e658fe6503e125fd1/astropy-6.1.7-cp310-cp310-win_amd64.whl", hash = "sha256:8d7f6727689288ee08fc0a4a297fc7e8089d01718321646bd00fea0906ad63dc", size = 6394810 }, + { url = "https://files.pythonhosted.org/packages/4f/5e/d31204823764f6e5fa4820c1b4f49f8eef7cf691b796ec389f41b4f5a699/astropy-6.1.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09edca01276ee63f7b2ff511da9bfb432068ba3242e27ef27d76e5a171087b7e", size = 6531221 }, + { url = "https://files.pythonhosted.org/packages/22/e2/ae5dd6d9272e41619d85df4e4a03cf06acea8bcb44c42fe67e5cd04ae131/astropy-6.1.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:072f62a67992393beb016dc80bee8fb994fda9aa69e945f536ed8ac0e51291e6", size = 6409477 }, + { url = "https://files.pythonhosted.org/packages/01/ed/9bc17beb457943ee04b8c85614ddb4a64a4a91597340dca28332e112209d/astropy-6.1.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2706156d3646f9c9a7fc810475d8ab0df4c717beefa8326552576a0f8ddca20", size = 10150734 }, + { url = "https://files.pythonhosted.org/packages/39/38/1c5263f0d775def518707ccd1cf9d4df1d99d523fc148df9e38aa5ba9d54/astropy-6.1.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcd99e627692f8e58bb3097d330bfbd109a22e00dab162a67f203b0a0601ad2c", size = 10210679 }, + { url = "https://files.pythonhosted.org/packages/32/d1/7365e16b0158f755977a5bdbd329df40a9772b0423a1d5075aba9246673f/astropy-6.1.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b0ebbcb637b2e9bcb73011f2b7890d7a3f5a41b66ccaad7c28f065e81e28f0b2", size = 10245960 }, + { url = "https://files.pythonhosted.org/packages/e9/b6/4dc6f9ef1c17738b8ebd8922bc1c6fec48542ccfe5124b6719737b012b8c/astropy-6.1.7-cp311-cp311-win32.whl", hash = "sha256:192b12ede49cd828362ab1a6ede2367fe203f4d851804ec22fa92e009a524281", size = 6272124 }, + { url = "https://files.pythonhosted.org/packages/ba/c6/b5f33597bfbc1afad0640b20000633127dfa0a4295b607a0439f45546d9a/astropy-6.1.7-cp311-cp311-win_amd64.whl", hash = "sha256:3cac64bcdf570c947019bd2bc96711eeb2c7763afe192f18c9551e52a6c296b2", size = 6396627 }, + { url = "https://files.pythonhosted.org/packages/46/2b/007c888fead170c714ecdcf56bc59e8d3252776bd3f16e1797158a46f65d/astropy-6.1.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2a8bcbb1306052cc38c9eed2c9331bfafe2582b499a7321946abf74b26eb256", size = 6535604 }, + { url = "https://files.pythonhosted.org/packages/8e/4c/cc30c9b1440f4a2f1f52845873ae3f8f7c4343261e516603a35546574ed7/astropy-6.1.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eaf88878684f9d31aff36475c90d101f4cff22fdd4fd50098d9950fd56994df7", size = 6415117 }, + { url = "https://files.pythonhosted.org/packages/12/2d/9985b8b4225c2495c4e64713d1630937c83af863db606d12676b72b4f651/astropy-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb8cd231e53556e4eebe0393ea95a8cea6b2ff4187c95ac4ff8b17e7a8da823", size = 10177861 }, + { url = "https://files.pythonhosted.org/packages/b7/b6/63ccb085757638d15f0f9d6f2dffaccce7785236fe8bf23e4b380a333ce0/astropy-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ad36334d138a4f71d6fdcf225a98ad1dad6c343da4362d5a47a71f5c9da3ca9", size = 10258014 }, + { url = "https://files.pythonhosted.org/packages/c8/ee/a6af891802de463f70e3fddf09f3aeb1d46dde87885e2245d25a2ac46948/astropy-6.1.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dd731c526869d0c68507be7b31dd10871b7c44d310bb5495476505560c83cd33", size = 10277363 }, + { url = "https://files.pythonhosted.org/packages/dd/98/b253583f9de7033f03a7c5f5314b9e93177725a2020e0f36d338d242bf0e/astropy-6.1.7-cp312-cp312-win32.whl", hash = "sha256:662bacd7ae42561e038cbd85eea3b749308cf3575611a745b60f034d3350c97a", size = 6271741 }, + { url = "https://files.pythonhosted.org/packages/7a/63/e1b5f01e6735ed8f9d62d3eed5f226bc0ab516ab8558ffaccf6d4185f91d/astropy-6.1.7-cp312-cp312-win_amd64.whl", hash = "sha256:5b4d02a98a0bf91ff7fd4ef0bd0ecca83c9497338cb88b61ec9f971350688222", size = 6396352 }, + { url = "https://files.pythonhosted.org/packages/73/9d/21d2e61080a81e7e1f5e5006204a76e70588aa1a88aa9044c2d203578d07/astropy-6.1.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbeaf04427987c0c6fa2e579eb40011802b06fba6b3a7870e082d5c693564e1b", size = 6528360 }, + { url = "https://files.pythonhosted.org/packages/d5/3e/b999ec6cd607c512e66d8a138443361eb88899760c7cb8517a66155732ee/astropy-6.1.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab6e88241a14185b9404b02246329185b70292984aa0616b20a0628dfe4f4ebb", size = 6407905 }, + { url = "https://files.pythonhosted.org/packages/db/2d/44557c63688c2ed03d0d72b4f27fc30fc1ea250aeb5ebd939796c5f98bee/astropy-6.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0529c75565feaabb629946806b4763ae7b02069aeff4c3b56a69e8a9e638500", size = 10106849 }, + { url = "https://files.pythonhosted.org/packages/66/bc/993552eb932dec528fe6b95f511e918473ea4406dee4b17c223f3fd8a919/astropy-6.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5ec347631da77573fc729ba04e5d89a3bc94500bf6037152a2d0f9965ae1ce", size = 10194766 }, + { url = "https://files.pythonhosted.org/packages/9f/f3/3c5282762c8a5746e7752e46a1e328c79a5d0186d96cfd0995bdf976e1f9/astropy-6.1.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc496f87aaccaa5c6624acc985b8770f039c5bbe74b120c8ed7bad3698e24e1b", size = 10219291 }, + { url = "https://files.pythonhosted.org/packages/d8/52/949bb79df9c03f56d0ae93ac62f2616fe3e67db51677bf412473bf6d077e/astropy-6.1.7-cp313-cp313-win32.whl", hash = "sha256:b1e01d534383c038dbf8664b964fa4ea818c7419318830d3c732c750c64115c6", size = 6269501 }, + { url = "https://files.pythonhosted.org/packages/a1/da/f369561a67061dd42e13c7f758b393ae90319dbbcf7e301a18ce3fa43ec6/astropy-6.1.7-cp313-cp313-win_amd64.whl", hash = "sha256:af08cf2b0368f1ea585eb26a55d99a2de9e9b0bd30aba84b5329059c3ec33590", size = 6393207 }, +] + +[[package]] +name = "astropy" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "astropy-iers-data", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pyerfa", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/12/a1c582b3f9df5e2680eae0ed82c8057ae06d6130a1f7a5c0770fa899737e/astropy-7.0.1.tar.gz", hash = "sha256:392feeb443b2437cd4c2e0641a65e0f15ba791e148e9b1e5ed7de7dfcb38e460", size = 7139539 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/79/ab4edcdb5928ed636b24d6e721460880d0bb6a7a2bca228d06abb5d2a5e4/astropy-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b16c5e3cd30ecedc79401a931eaeae3b9f2fc17f19ea26693c6c424cda74bd0e", size = 6574981 }, + { url = "https://files.pythonhosted.org/packages/57/34/87a0feca0214f21092c75381b046b9eb71cf56ea406f6148aa34cf30c3bb/astropy-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11c680207674a53afd603addf5640809bb670f72e9f9a6f023b862040ba7e100", size = 6499230 }, + { url = "https://files.pythonhosted.org/packages/4e/08/e112c26a69ce00238d3d03b5d200d86816b9db5b582169957a9102416aae/astropy-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e930aa618a1df80ecc38ecc594ca1e7508909c79c4b1145e9e91e8695fd163c", size = 10251470 }, + { url = "https://files.pythonhosted.org/packages/75/9d/1e150830a5491b684e2bb8c8e7224702ac57c101b4e208ed8f84eeedbf5a/astropy-7.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b4831c5b8a8e6c62afc095d9f65e6c023f058821c1fcc829adc2d941090cd2a", size = 10310513 }, + { url = "https://files.pythonhosted.org/packages/5c/f0/2c4cd9fbbdb222aaafe902fe39e62851ab8fe25b394f328d68df0d0f2301/astropy-7.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2245f23020db2382f187a880dd903f1d4747cbdce6af8f299bc6d4f256b382bf", size = 10345694 }, + { url = "https://files.pythonhosted.org/packages/5a/0c/f0c7624e0db2b81ff7e2bb8fbf3c0403c9f4e9ed29a99132af51d26b54b6/astropy-7.0.1-cp311-cp311-win32.whl", hash = "sha256:91bec2ac62f759300279fd98f3baf2fe33082729045d80c231948d6df0cceacc", size = 6364686 }, + { url = "https://files.pythonhosted.org/packages/65/61/b85c917adbd0a69ebe0e512285abea1ae81e1a713ae55452b4f20afdb1e7/astropy-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ae8960fe690f62672afff723d5e7d25ccd054c941c3ded96471d4d62563864e", size = 6489635 }, + { url = "https://files.pythonhosted.org/packages/74/e2/3dc27026a098171cf3f88d964dfdf39c70c75fd205624f2f9be8381268f7/astropy-7.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b075f4b8034a3e40d417c079232f1adb613a40e3b8d7cebdb05278666bfeb3ca", size = 6581263 }, + { url = "https://files.pythonhosted.org/packages/2d/45/0ae6513fc819a9223c5a779b2955ceb27ab5ad57c44e16abcc9fbaee838c/astropy-7.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71bb61871859635653e0530ca152c379882473ff4aabf2484a848ead94efe248", size = 6504874 }, + { url = "https://files.pythonhosted.org/packages/ae/f8/2d151d9476eb8797923164aee9764873c28be3654c7c60eae5d33f916d30/astropy-7.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1692857a3f21cecadefe89e4371e9c5a2d03e80173f4bb01dcbe3e3213ad1303", size = 10279377 }, + { url = "https://files.pythonhosted.org/packages/ed/70/930a24bd3f2046ba2c4b8a914d0349ffb17760d044f2a4e8c3195b984851/astropy-7.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062f9dc6549884699c551310a21434ff650c9a1cb6c0eea9476ead456f8a8500", size = 10358814 }, + { url = "https://files.pythonhosted.org/packages/22/07/28bc41436ada5e614a583371780d03f6377aa845450d0b04d544dee5c49c/astropy-7.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9cd02ef44007e0d6f27f0136410253a64b1ae56a2333c7071fd775c6feea8e12", size = 10377323 }, + { url = "https://files.pythonhosted.org/packages/4e/56/175acf7145af6c77cfeca339837293b2dee06936280bf841b6df8da30c92/astropy-7.0.1-cp312-cp312-win32.whl", hash = "sha256:ba9c09cb1ba9c126b40cbbbe9273b4e6f2b0403d268f64a15ed976f77754372b", size = 6364259 }, + { url = "https://files.pythonhosted.org/packages/69/a8/ae5bb7849274b04248817fc8c885c0966c02c7d20a9a7f9e7388fa9f091f/astropy-7.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:02259457097a3bad3e50975d67bd7586753b2a69d680f071be5c8da439e0f2c4", size = 6489380 }, + { url = "https://files.pythonhosted.org/packages/e2/f0/b4a484970e6227ea94cacea93d7dbd38bbef9c368e60ef3a6e00fe21d7b8/astropy-7.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:012e4331535dae6f43ddf86295b05e711e1ff6dec7af9d77a887ef08e6cef60d", size = 6574160 }, + { url = "https://files.pythonhosted.org/packages/10/87/cb34793d62e33f97f61f2cec9e4651e8f14786e1810a78fae6e7209b6232/astropy-7.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0785533096b5682148ab79b077ab0e17f254be17c9fa12cb7789b8109044fe95", size = 6497653 }, + { url = "https://files.pythonhosted.org/packages/b0/0c/4e8552bcfc4d12f9e2953f39336c554527261361277968aeada918208285/astropy-7.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5949792533238a601597574ac85599f0fab2db2480e0121c58121d808626698f", size = 10208169 }, + { url = "https://files.pythonhosted.org/packages/9b/a3/33546e62bb936d8dcceb24e7ef7540005c1dfbbe341715b217d2867e7cc2/astropy-7.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a367d334b789934b975565be470eb1043b3419508bd8e15a882775925e30d2ce", size = 10295439 }, + { url = "https://files.pythonhosted.org/packages/1b/a8/17246827967f6b4b6f5fa69ae1bb4a6e26370e3eb0d64185da2c3fa3911a/astropy-7.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:43c6dac6454e1056649268e79c3f595ecf079ad762936c6ce5c738ee1cff53de", size = 10319045 }, + { url = "https://files.pythonhosted.org/packages/48/6c/31730227b985744c7a58f7ebad1768f9501b4f1f4923ccd58e6f13fa6308/astropy-7.0.1-cp313-cp313-win32.whl", hash = "sha256:b458e4ac2466cb8bfeb0a9ed1fa7e96a9955d03902c1e5ce4ec8881bdfefaf4a", size = 6362116 }, + { url = "https://files.pythonhosted.org/packages/53/ef/eeeb9eb42cf5a83abf704043fafe04417849997321a20fcd465f32ed4b19/astropy-7.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb20105eeec84f6bd8bbde4a86686a8f78424c3c46b6021fbf816e40dd8960", size = 6486463 }, +] + +[[package]] +name = "astropy-iers-data" +version = "0.2025.2.17.0.34.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/8f/5b84d3b0cb98708d5bb8b4f1442ca9bdfd3dc1ffd4a1cde715e714e3ff85/astropy_iers_data-0.2025.2.17.0.34.13.tar.gz", hash = "sha256:34b685447a32e63f43811adcee2588bcd55dd77dfc454a05c25f0bbf80066960", size = 1893113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/e2/496d0136d7b1446ad2cfcd4aacae8220301d9789400a916da8d79019c487/astropy_iers_data-0.2025.2.17.0.34.13-py3-none-any.whl", hash = "sha256:2024e3251e1360cdbd978ccf19152610396d2324f5b2af19746e903e243b7566", size = 1945936 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/67/81dc41ec8f548c365d04a29f1afd492d3176b372c33e47fa2a45a01dc13a/coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8", size = 208345 }, + { url = "https://files.pythonhosted.org/packages/33/43/17f71676016c8829bde69e24c852fef6bd9ed39f774a245d9ec98f689fa0/coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879", size = 208775 }, + { url = "https://files.pythonhosted.org/packages/86/25/c6ff0775f8960e8c0840845b723eed978d22a3cd9babd2b996e4a7c502c6/coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe", size = 237925 }, + { url = "https://files.pythonhosted.org/packages/b0/3d/5f5bd37046243cb9d15fff2c69e498c2f4fe4f9b42a96018d4579ed3506f/coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674", size = 235835 }, + { url = "https://files.pythonhosted.org/packages/b5/f1/9e6b75531fe33490b910d251b0bf709142e73a40e4e38a3899e6986fe088/coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb", size = 236966 }, + { url = "https://files.pythonhosted.org/packages/4f/bc/aef5a98f9133851bd1aacf130e754063719345d2fb776a117d5a8d516971/coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c", size = 236080 }, + { url = "https://files.pythonhosted.org/packages/eb/d0/56b4ab77f9b12aea4d4c11dc11cdcaa7c29130b837eb610639cf3400c9c3/coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c", size = 234393 }, + { url = "https://files.pythonhosted.org/packages/0d/77/28ef95c5d23fe3dd191a0b7d89c82fea2c2d904aef9315daf7c890e96557/coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e", size = 235536 }, + { url = "https://files.pythonhosted.org/packages/29/62/18791d3632ee3ff3f95bc8599115707d05229c72db9539f208bb878a3d88/coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425", size = 211063 }, + { url = "https://files.pythonhosted.org/packages/fc/57/b3878006cedfd573c963e5c751b8587154eb10a61cc0f47a84f85c88a355/coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa", size = 211955 }, + { url = "https://files.pythonhosted.org/packages/64/2d/da78abbfff98468c91fd63a73cccdfa0e99051676ded8dd36123e3a2d4d5/coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015", size = 208464 }, + { url = "https://files.pythonhosted.org/packages/31/f2/c269f46c470bdabe83a69e860c80a82e5e76840e9f4bbd7f38f8cebbee2f/coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45", size = 208893 }, + { url = "https://files.pythonhosted.org/packages/47/63/5682bf14d2ce20819998a49c0deadb81e608a59eed64d6bc2191bc8046b9/coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/6a/b6/6b6631f1172d437e11067e1c2edfdb7238b65dff965a12bce3b6d1bf2be2/coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0", size = 239230 }, + { url = "https://files.pythonhosted.org/packages/c7/01/9cd06cbb1be53e837e16f1b4309f6357e2dfcbdab0dd7cd3b1a50589e4e1/coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f", size = 241013 }, + { url = "https://files.pythonhosted.org/packages/4b/26/56afefc03c30871326e3d99709a70d327ac1f33da383cba108c79bd71563/coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/dd/ea/88a1ff951ed288f56aa561558ebe380107cf9132facd0b50bced63ba7238/coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d", size = 238462 }, + { url = "https://files.pythonhosted.org/packages/6e/d4/1d9404566f553728889409eff82151d515fbb46dc92cbd13b5337fa0de8c/coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba", size = 239307 }, + { url = "https://files.pythonhosted.org/packages/12/c1/e453d3b794cde1e232ee8ac1d194fde8e2ba329c18bbf1b93f6f5eef606b/coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f", size = 211117 }, + { url = "https://files.pythonhosted.org/packages/d5/db/829185120c1686fa297294f8fcd23e0422f71070bf85ef1cc1a72ecb2930/coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558", size = 212019 }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 }, + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 }, + { url = "https://files.pythonhosted.org/packages/7a/7f/05818c62c7afe75df11e0233bd670948d68b36cdbf2a339a095bc02624a8/coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf", size = 200558 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docutils" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "firefly-client" +version = "3.3.0" +source = { editable = "." } +dependencies = [ + { name = "requests" }, + { name = "websocket-client" }, +] + +[package.optional-dependencies] +astropy = [ + { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "astropy", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +docs = [ + { name = "myst-parser" }, + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, + { name = "sphinx-automodapi" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-container" }, + { name = "pytest-cov" }, + { name = "pytest-doctestplus" }, + { name = "pytest-mock" }, + { name = "pytest-xdist" }, + { name = "tox" }, + { name = "tox-uv" }, +] + +[package.metadata] +requires-dist = [ + { name = "astropy", marker = "extra == 'astropy'", specifier = ">=6" }, + { name = "myst-parser", marker = "extra == 'docs'" }, + { name = "pydata-sphinx-theme", marker = "extra == 'docs'" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.4" }, + { name = "pytest-container", marker = "extra == 'test'", specifier = ">=0.4.3" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=6.0.0" }, + { name = "pytest-doctestplus", marker = "extra == 'test'", specifier = ">=1.4.0" }, + { name = "pytest-mock", marker = "extra == 'test'", specifier = ">=3.14.0" }, + { name = "pytest-xdist", marker = "extra == 'test'", specifier = ">=3.6.1" }, + { name = "requests" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = "~=7.1.0" }, + { name = "sphinx-automodapi", marker = "extra == 'docs'" }, + { name = "tox", marker = "extra == 'test'", specifier = ">=4.0" }, + { name = "tox-uv", marker = "extra == 'test'", specifier = ">=1.20" }, + { name = "websocket-client" }, +] +provides-extras = ["docs", "test", "astropy"] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, +] + +[[package]] +name = "numpy" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/e1/1816d5d527fa870b260a1c2c5904d060caad7515637bd54f495a5ce13ccd/numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", size = 21232911 }, + { url = "https://files.pythonhosted.org/packages/29/46/9f25dc19b359f10c0e52b6bac25d3181eb1f4b4d04c9846a32cf5ea52762/numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", size = 14371955 }, + { url = "https://files.pythonhosted.org/packages/72/d7/de941296e6b09a5c81d3664ad912f1496a0ecdd2f403318e5e35604ff70f/numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", size = 5410476 }, + { url = "https://files.pythonhosted.org/packages/36/ce/55f685995110f8a268fdca0f198c9a84fa87b39512830965cc1087af6391/numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", size = 6945730 }, + { url = "https://files.pythonhosted.org/packages/4f/84/abdb9f6e22576d89c259401c3234d4755b322539491bbcffadc8bcb120d3/numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", size = 14350752 }, + { url = "https://files.pythonhosted.org/packages/e9/88/3870cfa9bef4dffb3a326507f430e6007eeac258ebeef6b76fc542aef66d/numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", size = 16399386 }, + { url = "https://files.pythonhosted.org/packages/02/10/3f629682dd0b457525c131945329c4e81e2dadeb11256e6ce4c9a1a6fb41/numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b", size = 15561826 }, + { url = "https://files.pythonhosted.org/packages/da/18/fd35673ba9751eba449d4ce5d24d94e3b612cdbfba79348da71488c0b7ac/numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", size = 18188593 }, + { url = "https://files.pythonhosted.org/packages/ce/4c/c0f897b580ea59484b4cc96a441fea50333b26675a60a1421bc912268b5f/numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", size = 6590421 }, + { url = "https://files.pythonhosted.org/packages/e5/5b/aaabbfc7060c5c8f0124c5deb5e114a3b413a548bbc64e372c5b5db36165/numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", size = 12925667 }, + { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, + { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, + { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, + { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, + { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, + { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, + { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, + { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, + { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, + { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, + { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, + { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, + { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, + { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, + { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, + { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, + { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, + { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, + { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, + { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, + { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, + { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, + { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, + { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, + { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, + { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, + { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, + { url = "https://files.pythonhosted.org/packages/0a/b5/a7839f5478be8f859cb880f13d90fcfe4b0ec7a9ebaff2bcc30d96760596/numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", size = 21064244 }, + { url = "https://files.pythonhosted.org/packages/29/e8/5da32ffcaa7a72f7ecd82f90c062140a061eb823cb88e90279424e515cf4/numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", size = 6809418 }, + { url = "https://files.pythonhosted.org/packages/a8/a9/68aa7076c7656a7308a0f73d0a2ced8c03f282c9fd98fa7ce21c12634087/numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", size = 16215461 }, + { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264 }, +] + +[[package]] +name = "pyerfa" +version = "2.0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/39/63cc8291b0cf324ae710df41527faf7d331bce573899199d926b3e492260/pyerfa-2.0.1.5.tar.gz", hash = "sha256:17d6b24fe4846c65d5e7d8c362dcb08199dc63b30a236aedd73875cc83e1f6c0", size = 818430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/d9/3448a57cb5bd19950de6d6ab08bd8fbb3df60baa71726de91d73d76c481b/pyerfa-2.0.1.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b282d7c60c4c47cf629c484c17ac504fcb04abd7b3f4dfcf53ee042afc3a5944", size = 341818 }, + { url = "https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be1aeb70390dd03a34faf96749d5cabc58437410b4aab7213c512323932427df", size = 329370 }, + { url = "https://files.pythonhosted.org/packages/cb/96/b6210fc624123c8ae13e1eecb68fb75e3f3adff216d95eee1c7b05843e3e/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0603e8e1b839327d586c8a627cdc634b795e18b007d84f0cda5500a0908254e", size = 692794 }, + { url = "https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e43c7194e3242083f2350b46c09fd4bf8ba1bcc0ebd1460b98fc47fe2389906", size = 738711 }, + { url = "https://files.pythonhosted.org/packages/b9/f5/ff91ee77308793ae32fa1e1de95e9edd4551456dd888b4e87c5938657ca5/pyerfa-2.0.1.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07b80cd70701f5d066b1ac8cce406682cfcd667a1186ec7d7ade597239a6021d", size = 722966 }, + { url = "https://files.pythonhosted.org/packages/2c/56/b22b35c8551d2228ff8d445e63787112927ca13f6dc9e2c04f69d742c95b/pyerfa-2.0.1.5-cp39-abi3-win32.whl", hash = "sha256:d30b9b0df588ed5467e529d851ea324a67239096dd44703125072fd11b351ea2", size = 339955 }, + { url = "https://files.pythonhosted.org/packages/b4/11/97233cf23ad5411ac6f13b1d6ee3888f90ace4f974d9bf9db887aa428912/pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0", size = 349410 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-container" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "filelock" }, + { name = "pytest" }, + { name = "pytest-testinfra" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/8d/443c8274fc765b58256ff92bb62a9a4359ac450ee5dc3f1b11b9babab7b4/pytest_container-0.4.3.tar.gz", hash = "sha256:4b9fb1eb398ebbd596cc50b5c7ca9eb7508363ebfda779bcacab3ea131c2866f", size = 40298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/04/99e05194194e3f21bc627e700e1edc1aea91f1161bbb32728bea5c73c128/pytest_container-0.4.3-py3-none-any.whl", hash = "sha256:94be6bd43ece7a6a9fa7781d8497f96db2f7d77864addbe2d57ee59a41649721", size = 44127 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "pytest-doctestplus" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/e5/97c4bc17e93d5caf6b37ebab0bdfd668c1c62d575e6e1e5040bfa759b4f2/pytest_doctestplus-1.4.0.tar.gz", hash = "sha256:df83832b1d11288572df2ee4c7cccdb421d812b8038a658bb514c9c62bdbd626", size = 47566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/08/0e0e38a6046f91ad6ae352c0639c1b6dd90e2cd53ab2d2282d1d231535fb/pytest_doctestplus-1.4.0-py3-none-any.whl", hash = "sha256:cfbae130ec90d4a2831819bbbfd097121b8e55f1e4d20a47ea992e4eaad2539a", size = 25236 }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + +[[package]] +name = "pytest-testinfra" +version = "10.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/15/354adf3dd9635b554b27eb0167216e02bebed1796035991999c32b5081af/pytest-testinfra-10.1.1.tar.gz", hash = "sha256:a876f1453a01b58d94d9d936dd50344c2c01ac7880a2b41d15bdf233aed9cf1f", size = 86567 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/a8/e66b4ef547c8ff86b9c3a4744c296b879a2c6e98f4796f4bee66b911762b/pytest_testinfra-10.1.1-py3-none-any.whl", hash = "sha256:b990dc7d77b49a1bba24818fbff49b6171d8c46d606fb5ca86b937de690d7062", size = 76765 }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "sphinx" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/01/688bdf9282241dca09fe6e3a1110eda399fa9b10d0672db609e37c2e7a39/sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f", size = 6828258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe", size = 3169543 }, +] + +[[package]] +name = "sphinx-automodapi" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/38/ffc5c1dc26776f1d037021ebcffdfaca74644ec5e5cf5a7735149c51df90/sphinx_automodapi-0.18.0.tar.gz", hash = "sha256:7bf9d9a2cb67a5389c51071cfd86674ca3892ca5d5943f95de4553d6f35dddae", size = 51297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/96/5492f177d6bb95c5418315ab1c517283f06895b4066f2837ba215be42c84/sphinx_automodapi-0.18.0-py3-none-any.whl", hash = "sha256:022860385590768f52d4f6e19abb83b2574772d2721fb4050ecdb6e593a1a440", size = 88516 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tox" +version = "4.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/7b/97f757e159983737bdd8fb513f4c263cd411a846684814ed5433434a1fa9/tox-4.24.1.tar.gz", hash = "sha256:083a720adbc6166fff0b7d1df9d154f9d00bfccb9403b8abf6bc0ee435d6a62e", size = 194742 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/04/b0d1c1b44c98583cab9eabb4acdba964fdf6b6c597c53cfb8870fd08cbbf/tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75", size = 171829 }, +] + +[[package]] +name = "tox-uv" +version = "1.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tox" }, + { name = "uv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/73/25a6eaad23be1443b7038a4bb90dd2d39f9fef48602d014a043728abd917/tox_uv-1.24.1.tar.gz", hash = "sha256:77e447177f7eabb5cc066639fc834df01f36f2aac7c7b84b4a8b194318a98eef", size = 20585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/6e/487bb2e7cb9d708d25aa2015def5ccf88073a7e62bc7f025a45e38f0e92f/tox_uv-1.24.1-py3-none-any.whl", hash = "sha256:bffaa804af328b080e41d4bd23ba7d2c4f11926e6956bd698cb85006cc1d0713", size = 15417 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "uv" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/118e10d91981b85f47b27d089782a6598a9584ff607bffb8e2f6be1f1245/uv-0.6.2.tar.gz", hash = "sha256:d696a4f3d4a3ac1b305255e8814ae3a147ea3428a977bb3b4335a339941799bc", size = 3066291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/cf/9c3c9a427c7ecc37be238c4433188614b3d342191c0299c632f512d493ff/uv-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:d501ae16fb33969b12a64ac7b9c49d672b8c3964026c5dcaee3b1dcd50a6a22c", size = 15513992 }, + { url = "https://files.pythonhosted.org/packages/86/01/1e1f88826d92d11f2232f96eef190574a4edb470546a141bba652cd37240/uv-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c13ca920d87dc00721a86ac3d19667cff5435b369d21e3d6df76b373d8fa8df", size = 15659547 }, + { url = "https://files.pythonhosted.org/packages/ee/40/59e9c03431d4c82420e081f92719e5784db8f1c92a25b2abdfe6ac645b7e/uv-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f24e119d338bae32b5a604585b7b518036fba556e2c2d9dbd2d7cf1411213b57", size = 14589044 }, + { url = "https://files.pythonhosted.org/packages/11/8b/5d9f9f4e3969d6a2c9ce9a0b4a85ecb8ca89bf5c00e9ec097cf472abb2a2/uv-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:1db90b728a173926e2018b89df776a373b1e50520466f61e0dbf05f9a64a6db5", size = 15034328 }, + { url = "https://files.pythonhosted.org/packages/f3/ba/f31fd6af8f70b21d9e0b7cca0241a8f10e03d24862f49f93fbc5ff1e4fce/uv-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d23fb9cd41aecb31845e884d0bfde243e04e763abeab3532138321b4ebe7437c", size = 15275180 }, + { url = "https://files.pythonhosted.org/packages/aa/3b/358cfea4265a0966fafa7934ed0f9f1fb031d7ebbe8a15e02a308afff6ad/uv-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0a1d95fd1539c05de434259fafcee0b6852900d4178e94b3b6b6b06438b60c", size = 15969503 }, + { url = "https://files.pythonhosted.org/packages/57/f5/840d8fb46c1cf723e1b7168832de52e58d86764aa625c2100b35a27261af/uv-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2f0dc9a0564b31d4efdee317c176a23bbe7e61aec6d281a331ba6ae32f828ff", size = 16950563 }, + { url = "https://files.pythonhosted.org/packages/f6/37/75c5ff09db56c34f0f5d3d55dd4188e52d09219ef76bfe176dae58ed5f4a/uv-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:326aff8c4fb8153e2384e79904c27b1c9d4c3a5879b53a6fbc2da3283fda321d", size = 16631562 }, + { url = "https://files.pythonhosted.org/packages/9d/5f/91bfae5ecf9f6c5f4754aa794159acc77245a53233a966865ae4974e5cdf/uv-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8763f310a473f46c0226f5e08a876bd34de121ac370cc7294a5397a13a18d8a", size = 20994598 }, + { url = "https://files.pythonhosted.org/packages/8d/39/17f77b4b5f1a1e579d9ce94859aada9418c9ebcaa227b54b10648218bafa/uv-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2e421947ef889e6c8913992c560d611826464eabc78f8f702a5eff824aabc7", size = 16367280 }, + { url = "https://files.pythonhosted.org/packages/a7/6b/fbd9794e1344b299e02993322f44b500f4d66ecdb83860e2fcf35d8cac2c/uv-0.6.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7dd26dabd918e5648ecf94fb7c0787db954237e34ea3bdd944b98d007b44c3a5", size = 15317824 }, + { url = "https://files.pythonhosted.org/packages/51/a0/9249a55365c2f9781243a7f35c3a01864b19aa9a62b1fc50b7231793346e/uv-0.6.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:f3719da2e59403783eab634a6238b90051fc65379e02c10b9ca1b32b26d35f77", size = 15228644 }, + { url = "https://files.pythonhosted.org/packages/27/76/790b3d9c0b9ecd9ab6c1b7e904c36d470685c70d0b21a134b026452e0fcc/uv-0.6.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:b435687e5c26a64858ea842fbb4b35ced8e8741a99d1b75d0c0143462e956db9", size = 15608612 }, + { url = "https://files.pythonhosted.org/packages/05/b6/79961374b2318461b4dfc0e565d63281bf788fea93fc81b2d1738847aec2/uv-0.6.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0f1e8e15c92607862e72e0467a31947af7b9aef93924072e9b4d5dcb5633d374", size = 16480962 }, + { url = "https://files.pythonhosted.org/packages/68/20/df7788bde9d114c501cd8ebb60235be07ff0fb0dc26fa1e7e99ada251d73/uv-0.6.2-py3-none-win32.whl", hash = "sha256:52b7452f4c523b9875de53ba73df87acd1cdea36640281d0d80c8074eda42f16", size = 15717804 }, + { url = "https://files.pythonhosted.org/packages/e1/0a/fc966f859b6252050c71e1afcdce116c8ef3513f8b423bb3ca05fb13485d/uv-0.6.2-py3-none-win_amd64.whl", hash = "sha256:5337cdb6ecc604d0cf36fe6799dd0479111b606009e6c29685d213c74eb40373", size = 17017798 }, + { url = "https://files.pythonhosted.org/packages/03/82/4318c4874c8dd59a0386e2bf0f4d09fc5bb4900349238828153235d387eb/uv-0.6.2-py3-none-win_arm64.whl", hash = "sha256:27ecb8f6ef796220062f31a12e2dc5dc7a14704aa1df0da2dfa3530346c7e3cc", size = 15923484 }, +] + +[[package]] +name = "virtualenv" +version = "20.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +]