Compare commits
9 Commits
v2.65.2.13
...
v2.65.0-cy
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f856cc9c4 | |||
| 14eec49a49 | |||
| 90750c58b7 | |||
| 0599695792 | |||
| ca58fc73f1 | |||
| d961bf5344 | |||
| 33fd9e63f1 | |||
| 2eeb3650f4 | |||
| 6f8f38c16e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -126,4 +126,5 @@ gitversion
|
||||
/locale/progress.json
|
||||
|
||||
# vscode settings
|
||||
.vscode
|
||||
.vscode
|
||||
eve.db
|
||||
|
||||
28
build.sh
Normal file
28
build.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building pyfa binary..."
|
||||
|
||||
# Ensure we're using the local venv
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
uv venv
|
||||
fi
|
||||
|
||||
# Install dependencies
|
||||
echo "Installing dependencies..."
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install pyinstaller
|
||||
|
||||
# Clean previous builds
|
||||
echo "Cleaning previous builds..."
|
||||
rm -rf build dist
|
||||
|
||||
# Build the binary
|
||||
echo "Building binary with PyInstaller..."
|
||||
uv run pyinstaller pyfa.spec
|
||||
|
||||
echo ""
|
||||
echo "Build complete! Binary is located at: dist/pyfa/pyfa.exe"
|
||||
echo "You can run it with: dist/pyfa/pyfa.exe"
|
||||
@@ -5,6 +5,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
import gui.globalEvents as GE
|
||||
from config import slotColourMap, slotColourMapDark
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingSlot
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.display import Display
|
||||
@@ -153,19 +154,22 @@ class ItemView(Display):
|
||||
# skip the event so the other handlers also get called
|
||||
event.Skip()
|
||||
|
||||
if self.marketBrowser.mode != 'charges':
|
||||
return
|
||||
|
||||
activeFitID = self.mainFrame.getActiveFit()
|
||||
# if it was not the active fitting that was changed, do not do anything
|
||||
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||
return
|
||||
|
||||
items = self.getChargesForActiveFit()
|
||||
|
||||
# update the UI
|
||||
self.updateItemStore(items)
|
||||
self.filterItemStore()
|
||||
# Handle charges mode
|
||||
if self.marketBrowser.mode == 'charges':
|
||||
items = self.getChargesForActiveFit()
|
||||
# update the UI
|
||||
self.updateItemStore(items)
|
||||
self.filterItemStore()
|
||||
return
|
||||
|
||||
# If "Fits" filter is active, re-filter the current view
|
||||
if self.marketBrowser.getFitsFilter():
|
||||
self.filterItemStore()
|
||||
|
||||
def updateItemStore(self, items):
|
||||
self.unfilteredStore = items
|
||||
@@ -197,13 +201,115 @@ class ItemView(Display):
|
||||
if btn.userSelected:
|
||||
selectedMetas.update(sMkt.META_MAP[btn.metaName])
|
||||
filteredItems = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas)
|
||||
|
||||
# Apply slot/fits filters - works IDENTICALLY to meta buttons (filters CURRENT VIEW only)
|
||||
activeSlotFilters = []
|
||||
fitsFilterActive = False
|
||||
for btn in self.marketBrowser.slotButtons:
|
||||
if btn.userSelected:
|
||||
if btn.filterType == "fits":
|
||||
fitsFilterActive = True
|
||||
elif btn.filterType == "slot":
|
||||
activeSlotFilters.append(btn.slotType)
|
||||
|
||||
# Apply fits filter
|
||||
if fitsFilterActive:
|
||||
filteredItems = self._filterByFits(filteredItems)
|
||||
|
||||
# Apply slot filters
|
||||
if activeSlotFilters:
|
||||
filteredItems = [item for item in filteredItems if Module.calculateSlot(item) in activeSlotFilters]
|
||||
|
||||
return filteredItems
|
||||
|
||||
def _filterByFits(self, items):
|
||||
"""Filter items by remaining CPU/PG - filters CURRENT VIEW only"""
|
||||
fitId = self.mainFrame.getActiveFit()
|
||||
if fitId is None:
|
||||
return []
|
||||
|
||||
fit = self.sFit.getFit(fitId)
|
||||
|
||||
# Get remaining CPU and power grid
|
||||
cpuOutput = fit.ship.getModifiedItemAttr("cpuOutput")
|
||||
powerOutput = fit.ship.getModifiedItemAttr("powerOutput")
|
||||
cpuUsed = fit.cpuUsed
|
||||
pgUsed = fit.pgUsed
|
||||
cpuRemaining = cpuOutput - cpuUsed
|
||||
pgRemaining = powerOutput - pgUsed
|
||||
|
||||
# Get remaining calibration (for rigs)
|
||||
calibrationCapacity = fit.ship.getModifiedItemAttr("upgradeCapacity")
|
||||
calibrationUsed = fit.calibrationUsed
|
||||
calibrationRemaining = None
|
||||
if calibrationCapacity is not None and calibrationCapacity > 0:
|
||||
calibrationRemaining = calibrationCapacity - calibrationUsed
|
||||
|
||||
fittingItems = []
|
||||
for item in items:
|
||||
# Check if item is a module (has a slot)
|
||||
slot = Module.calculateSlot(item)
|
||||
if slot is None:
|
||||
continue
|
||||
|
||||
# Rigs don't use CPU/power, they use calibration - check rig size and calibration
|
||||
if slot == FittingSlot.RIG:
|
||||
# Check if item can fit on the ship
|
||||
if not fit.canFit(item):
|
||||
continue
|
||||
# Check rig size compatibility with ship
|
||||
shipRigSize = fit.ship.getModifiedItemAttr("rigSize")
|
||||
itemRigSize = item.attributes.get("rigSize")
|
||||
if shipRigSize is not None and itemRigSize is not None:
|
||||
if shipRigSize != itemRigSize.value:
|
||||
continue
|
||||
# Check calibration requirement
|
||||
if calibrationRemaining is not None and calibrationRemaining > 0:
|
||||
itemCalibration = item.attributes.get("upgradeCost")
|
||||
if itemCalibration is not None:
|
||||
itemCalibrationValue = itemCalibration.value
|
||||
if itemCalibrationValue > calibrationRemaining:
|
||||
continue
|
||||
fittingItems.append(item)
|
||||
continue
|
||||
|
||||
# For non-rigs, check CPU and power requirements
|
||||
itemCpu = item.attributes.get("cpu")
|
||||
itemPower = item.attributes.get("power")
|
||||
|
||||
# Skip items without CPU or power (not modules)
|
||||
if itemCpu is None and itemPower is None:
|
||||
continue
|
||||
|
||||
# Check CPU requirement
|
||||
if itemCpu is not None:
|
||||
itemCpuValue = itemCpu.value
|
||||
if itemCpuValue > cpuRemaining:
|
||||
continue
|
||||
|
||||
# Check power requirement
|
||||
if itemPower is not None:
|
||||
itemPowerValue = itemPower.value
|
||||
if itemPowerValue > pgRemaining:
|
||||
continue
|
||||
|
||||
# Check if item can fit on the ship (most expensive check, do last)
|
||||
if not fit.canFit(item):
|
||||
continue
|
||||
|
||||
fittingItems.append(item)
|
||||
|
||||
return fittingItems
|
||||
|
||||
def setToggles(self):
|
||||
metaIDs = set()
|
||||
slotIDs = set()
|
||||
sMkt = self.sMkt
|
||||
for item in self.unfilteredStore:
|
||||
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
|
||||
slot = Module.calculateSlot(item)
|
||||
if slot is not None:
|
||||
slotIDs.add(slot)
|
||||
|
||||
for btn in self.marketBrowser.metaButtons:
|
||||
btn.reset()
|
||||
@@ -212,6 +318,20 @@ class ItemView(Display):
|
||||
btn.setMetaAvailable(True)
|
||||
else:
|
||||
btn.setMetaAvailable(False)
|
||||
|
||||
# Set toggles for slot/fits buttons
|
||||
for btn in self.marketBrowser.slotButtons:
|
||||
btn.reset()
|
||||
if btn.filterType == "fits":
|
||||
# Fits button is available if there's an active fit
|
||||
fitId = self.mainFrame.getActiveFit()
|
||||
btn.setMetaAvailable(fitId is not None)
|
||||
elif btn.filterType == "slot":
|
||||
# Slot button is available if items with that slot exist in current view
|
||||
if btn.slotType in slotIDs:
|
||||
btn.setMetaAvailable(True)
|
||||
else:
|
||||
btn.setMetaAvailable(False)
|
||||
|
||||
def scheduleSearch(self, event=None):
|
||||
self.searchTimer.Stop() # Cancel any pending timers
|
||||
|
||||
@@ -64,22 +64,61 @@ class MarketBrowser(wx.Panel):
|
||||
# Same fix as for search box on macs,
|
||||
# need some pixels of extra space or everything clips and is ugly
|
||||
p = wx.Panel(self)
|
||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
||||
p.SetSizer(box)
|
||||
vbox_panel = wx.BoxSizer(wx.VERTICAL)
|
||||
p.SetSizer(vbox_panel)
|
||||
vbox.Add(p, 0, wx.EXPAND)
|
||||
|
||||
# First row: meta buttons
|
||||
metaBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
vbox_panel.Add(metaBox, 0, wx.EXPAND)
|
||||
self.metaButtons = []
|
||||
btn = None
|
||||
for name in list(self.sMkt.META_MAP.keys()):
|
||||
btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
|
||||
setattr(self, name, btn)
|
||||
box.Add(btn, 1, wx.ALIGN_CENTER)
|
||||
metaBox.Add(btn, 1, wx.ALIGN_CENTER)
|
||||
btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton)
|
||||
btn.metaName = name
|
||||
self.metaButtons.append(btn)
|
||||
|
||||
# Second row: slot/fits filter buttons (BELOW meta buttons)
|
||||
slotBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
vbox_panel.Add(slotBox, 0, wx.EXPAND)
|
||||
self.slotButtons = []
|
||||
from eos.const import FittingSlot
|
||||
|
||||
# Fits button
|
||||
fitsBtn = MetaButton(p, wx.ID_ANY, "Fits", style=wx.BU_EXACTFIT)
|
||||
setattr(self, "fits", fitsBtn)
|
||||
slotBox.Add(fitsBtn, 1, wx.ALIGN_CENTER)
|
||||
fitsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
|
||||
fitsBtn.filterType = "fits"
|
||||
# Fits button starts deselected (checkbox, off by default)
|
||||
fitsBtn.setUserSelection(False)
|
||||
self.slotButtons.append(fitsBtn)
|
||||
|
||||
# High, Med, Low, Rig buttons
|
||||
slotMap = {
|
||||
FittingSlot.HIGH: "High",
|
||||
FittingSlot.MED: "Med",
|
||||
FittingSlot.LOW: "Low",
|
||||
FittingSlot.RIG: "Rig"
|
||||
}
|
||||
for slot, label in slotMap.items():
|
||||
slotBtn = MetaButton(p, wx.ID_ANY, label, style=wx.BU_EXACTFIT)
|
||||
setattr(self, "slot_%s" % label.lower(), slotBtn)
|
||||
slotBox.Add(slotBtn, 1, wx.ALIGN_CENTER)
|
||||
slotBtn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleSlotButton)
|
||||
slotBtn.filterType = "slot"
|
||||
slotBtn.slotType = slot
|
||||
# Slot buttons start deselected (unlike meta buttons which start selected)
|
||||
slotBtn.setUserSelection(False)
|
||||
self.slotButtons.append(slotBtn)
|
||||
|
||||
# Make itemview to set toggles according to list contents
|
||||
self.itemView.setToggles()
|
||||
|
||||
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5))
|
||||
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] * 2 + 10))
|
||||
|
||||
def toggleMetaButton(self, event):
|
||||
"""Process clicks on toggle buttons"""
|
||||
@@ -100,6 +139,42 @@ class MarketBrowser(wx.Panel):
|
||||
|
||||
self.itemView.filterItemStore()
|
||||
|
||||
def toggleSlotButton(self, event):
|
||||
"""Process clicks on slot/fits filter buttons"""
|
||||
clickedBtn = event.EventObject
|
||||
|
||||
# Fits button works as a checkbox (independent toggle)
|
||||
if clickedBtn.filterType == "fits":
|
||||
clickedBtn.setUserSelection(clickedBtn.GetValue())
|
||||
self.itemView.filterItemStore()
|
||||
return
|
||||
|
||||
# Slot buttons (High/Med/Low) work as radio buttons (mutually exclusive)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL:
|
||||
# Get only slot buttons (not fits)
|
||||
activeSlotBtns = [btn for btn in self.slotButtons if btn.GetValue() and btn.filterType == "slot"]
|
||||
if activeSlotBtns:
|
||||
clickedBtn.setUserSelection(clickedBtn.GetValue())
|
||||
self.itemView.filterItemStore()
|
||||
else:
|
||||
# Do 'nothing' if we're trying to turn last active button off
|
||||
# Keep button in the same state
|
||||
clickedBtn.setUserSelection(True)
|
||||
else:
|
||||
# Deselect all slot buttons, then select clicked one
|
||||
for btn in self.slotButtons:
|
||||
if btn.filterType == "slot":
|
||||
btn.setUserSelection(btn == clickedBtn)
|
||||
|
||||
self.itemView.filterItemStore()
|
||||
|
||||
def getFitsFilter(self):
|
||||
"""Check if Fits button is active"""
|
||||
for btn in self.slotButtons:
|
||||
if btn.filterType == "fits" and btn.userSelected:
|
||||
return True
|
||||
return False
|
||||
|
||||
def jump(self, item):
|
||||
self.mode = 'normal'
|
||||
self.marketView.jump(item)
|
||||
@@ -157,4 +232,3 @@ class MarketBrowser(wx.Panel):
|
||||
for btn in self.metaButtons:
|
||||
btn.setUserSelection(True)
|
||||
self.__mode = newMode
|
||||
|
||||
|
||||
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "pyfa"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"ruff>=0.14.8",
|
||||
]
|
||||
60
release.sh
Normal file
60
release.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Figuring out the tag..."
|
||||
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
|
||||
if [ -z "$TAG" ]; then
|
||||
# Get the latest tag
|
||||
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "v0.0.0")
|
||||
# Remove 'v' prefix if present
|
||||
LATEST_TAG=${LATEST_TAG#v}
|
||||
# Increment the patch version
|
||||
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
|
||||
VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
|
||||
TAG="v${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
|
||||
# Create a new tag
|
||||
git tag $TAG
|
||||
git push origin $TAG
|
||||
fi
|
||||
echo "Tag: $TAG"
|
||||
|
||||
echo "Building the binary..."
|
||||
./build.sh
|
||||
|
||||
echo "Creating release zip..."
|
||||
ZIP="pyfa-${TAG}-win.zip"
|
||||
cd dist/pyfa
|
||||
zip -r "../../${ZIP}" .
|
||||
cd ../..
|
||||
|
||||
echo "Creating a release..."
|
||||
TOKEN="$GITEA_API_KEY"
|
||||
GITEA="https://git.site.quack-lab.dev"
|
||||
REPO="dave/pyfa"
|
||||
|
||||
# Create a release
|
||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tag_name": "'"$TAG"'",
|
||||
"name": "'"$TAG"'",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}' \
|
||||
$GITEA/api/v1/repos/$REPO/releases)
|
||||
|
||||
# Extract the release ID
|
||||
echo $RELEASE_RESPONSE
|
||||
RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
|
||||
echo "Uploading the zip..."
|
||||
curl -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@${ZIP}" \
|
||||
"$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=${ZIP}"
|
||||
|
||||
echo "Release complete! ${ZIP} uploaded to ${TAG}"
|
||||
40
uv.lock
generated
Normal file
40
uv.lock
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "pyfa"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "ruff", specifier = ">=0.14.8" }]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user