pypetkitapi 1.7.4__tar.gz → 1.7.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/PKG-INFO +2 -2
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/client.py +26 -15
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/const.py +3 -1
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/containers.py +1 -1
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/exceptions.py +5 -1
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/feeder_container.py +25 -5
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/litter_container.py +3 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/medias.py +34 -36
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pyproject.toml +3 -3
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/LICENSE +0 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/README.md +0 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/__init__.py +0 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/command.py +0 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/py.typed +0 -0
- {pypetkitapi-1.7.4 → pypetkitapi-1.7.7}/pypetkitapi/water_fountain_container.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pypetkitapi
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.7
|
4
4
|
Summary: Python client for PetKit API
|
5
5
|
Home-page: https://github.com/Jezza34000/pypetkit
|
6
6
|
License: MIT
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
16
16
|
Requires-Dist: aiohttp (>=3.10.10,<4.0.0)
|
17
17
|
Requires-Dist: pycryptodome (>=3.19.1,<4.0.0)
|
18
|
-
Requires-Dist: pydantic (>=1.10.
|
18
|
+
Requires-Dist: pydantic (>=1.10.18,<2.0.0)
|
19
19
|
Description-Content-Type: text/markdown
|
20
20
|
|
21
21
|
# Petkit API Client
|
@@ -30,9 +30,11 @@ from pypetkitapi.const import (
|
|
30
30
|
from pypetkitapi.containers import AccountData, Device, Pet, RegionInfo, SessionInfo
|
31
31
|
from pypetkitapi.exceptions import (
|
32
32
|
PetkitAuthenticationError,
|
33
|
+
PetkitAuthenticationUnregisteredEmailError,
|
33
34
|
PetkitInvalidHTTPResponseCodeError,
|
34
35
|
PetkitInvalidResponseFormat,
|
35
36
|
PetkitRegionalServerNotFoundError,
|
37
|
+
PetkitSessionExpiredError,
|
36
38
|
PetkitTimeoutError,
|
37
39
|
PypetkitError,
|
38
40
|
)
|
@@ -87,6 +89,7 @@ class PetKitClient:
|
|
87
89
|
for region in response.get("list", []):
|
88
90
|
server = RegionInfo(**region)
|
89
91
|
if server.name.lower() == self.region or server.id.lower() == self.region:
|
92
|
+
self.region = server.id.lower()
|
90
93
|
self.req.base_url = server.gateway
|
91
94
|
_LOGGER.debug("Found matching server: %s", server)
|
92
95
|
return
|
@@ -206,6 +209,7 @@ class PetKitClient:
|
|
206
209
|
if account.device_list:
|
207
210
|
_LOGGER.debug("Devices in account: %s", account.device_list)
|
208
211
|
device_list.extend(account.device_list)
|
212
|
+
_LOGGER.debug("Found %s devices", len(account.device_list))
|
209
213
|
|
210
214
|
for device in device_list:
|
211
215
|
device_type = device.device_type.lower()
|
@@ -259,6 +263,8 @@ class PetKitClient:
|
|
259
263
|
| FeederRecord
|
260
264
|
| LitterRecord
|
261
265
|
| WaterFountainRecord
|
266
|
+
| PetOutGraph
|
267
|
+
| LitterStats
|
262
268
|
],
|
263
269
|
) -> None:
|
264
270
|
"""Fetch the device data from the PetKit servers."""
|
@@ -345,16 +351,14 @@ class PetKitClient:
|
|
345
351
|
return 0
|
346
352
|
return end - start
|
347
353
|
|
348
|
-
async def populate_pet_stats(self, stats_data: Litter
|
354
|
+
async def populate_pet_stats(self, stats_data: Litter) -> None:
|
349
355
|
"""Collect data from litter data to populate pet stats."""
|
350
|
-
if stats_data is None:
|
351
|
-
return
|
352
356
|
|
353
357
|
pets_list = await self.get_pets_list()
|
354
358
|
for pet in pets_list:
|
355
|
-
if stats_data.device_records:
|
359
|
+
if stats_data.device_type == T4 and stats_data.device_records:
|
356
360
|
await self._process_t4(pet, stats_data.device_records)
|
357
|
-
elif stats_data.device_pet_graph_out:
|
361
|
+
elif stats_data.device_type == T6 and stats_data.device_pet_graph_out:
|
358
362
|
await self._process_t6(pet, stats_data.device_pet_graph_out)
|
359
363
|
|
360
364
|
async def _process_t4(self, pet, device_records):
|
@@ -528,17 +532,24 @@ class PrepReq:
|
|
528
532
|
|
529
533
|
# Check for errors in the response
|
530
534
|
if ERR_KEY in response_json:
|
535
|
+
error_code = int(response_json[ERR_KEY].get("code", 0))
|
531
536
|
error_msg = response_json[ERR_KEY].get("msg", "Unknown error")
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
537
|
+
|
538
|
+
match error_code:
|
539
|
+
case 5:
|
540
|
+
raise PetkitSessionExpiredError(f"Session expired: {error_msg}")
|
541
|
+
case 122:
|
542
|
+
raise PetkitAuthenticationError(
|
543
|
+
f"Authentication failed: {error_msg}"
|
544
|
+
)
|
545
|
+
case 125:
|
546
|
+
raise PetkitAuthenticationUnregisteredEmailError(
|
547
|
+
f"Authentication failed: {error_msg}"
|
548
|
+
)
|
549
|
+
case _:
|
550
|
+
raise PypetkitError(
|
551
|
+
f"Request failed code : {error_code} details : {error_msg}"
|
552
|
+
)
|
542
553
|
|
543
554
|
# Check for success in the response
|
544
555
|
if RES_KEY in response_json:
|
@@ -31,7 +31,7 @@ CTW3 = "ctw3"
|
|
31
31
|
K2 = "k2"
|
32
32
|
|
33
33
|
DEVICES_LITTER_BOX = [T3, T4, T5, T6]
|
34
|
-
DEVICES_FEEDER = [FEEDER, FEEDER_MINI, D4, D4S, D4H, D4SH]
|
34
|
+
DEVICES_FEEDER = [FEEDER, FEEDER_MINI, D3, D4, D4S, D4H, D4SH]
|
35
35
|
DEVICES_WATER_FOUNTAIN = [W5, CTW3]
|
36
36
|
ALL_DEVICES = [*DEVICES_LITTER_BOX, *DEVICES_FEEDER, *DEVICES_WATER_FOUNTAIN]
|
37
37
|
|
@@ -131,3 +131,5 @@ class PetkitEndpoint(StrEnum):
|
|
131
131
|
MANUAL_FEED_MINI = "feedermini/save_dailyfeed"
|
132
132
|
MANUAL_FEED_FRESH_ELEMENT = "feeder/save_dailyfeed"
|
133
133
|
MANUAL_FEED_DUAL = "saveDailyFeed"
|
134
|
+
DAILY_FEED_AND_EAT = "dailyFeedAndEat" # D3
|
135
|
+
FEED_STATISTIC = "feedStatistic" # D4
|
@@ -70,7 +70,7 @@ class Pet(BaseModel):
|
|
70
70
|
pet_id: int = Field(alias="petId")
|
71
71
|
pet_name: str | None = Field(None, alias="petName")
|
72
72
|
id: int | None = None # Fictive field (for HA compatibility) copied from id
|
73
|
-
sn:
|
73
|
+
sn: str # Fictive field (for HA compatibility) copied from id
|
74
74
|
name: str | None = None # Fictive field (for HA compatibility) copied from pet_name
|
75
75
|
device_type: str = "pet" # Fictive field (for HA compatibility) fixed
|
76
76
|
firmware: str | None = None # Fictive field (for HA compatibility) fixed
|
@@ -11,10 +11,14 @@ class PetkitTimeoutError(PypetkitError):
|
|
11
11
|
"""Class for PyPetkit timeout exceptions."""
|
12
12
|
|
13
13
|
|
14
|
-
class
|
14
|
+
class PetkitSessionExpiredError(PypetkitError):
|
15
15
|
"""Class for PyPetkit connection exceptions."""
|
16
16
|
|
17
17
|
|
18
|
+
class PetkitAuthenticationUnregisteredEmailError(PypetkitError):
|
19
|
+
"""Exception raised when the email is not registered with Petkit."""
|
20
|
+
|
21
|
+
|
18
22
|
class PetkitRegionalServerNotFoundError(PypetkitError):
|
19
23
|
"""Exception raised when the specified region server is not found."""
|
20
24
|
|
@@ -5,7 +5,7 @@ from typing import Any, ClassVar
|
|
5
5
|
|
6
6
|
from pydantic import BaseModel, Field
|
7
7
|
|
8
|
-
from pypetkitapi.const import DEVICE_DATA, DEVICE_RECORDS, PetkitEndpoint
|
8
|
+
from pypetkitapi.const import D3, D4, DEVICE_DATA, DEVICE_RECORDS, PetkitEndpoint
|
9
9
|
from pypetkitapi.containers import CloudProduct, Device, FirmwareDetail, Wifi
|
10
10
|
|
11
11
|
|
@@ -89,6 +89,7 @@ class SettingsFeeder(BaseModel):
|
|
89
89
|
selected_sound: int | None = Field(None, alias="selectedSound")
|
90
90
|
smart_frame: int | None = Field(None, alias="smartFrame")
|
91
91
|
sound_enable: int | None = Field(None, alias="soundEnable")
|
92
|
+
surplus: int | None = None # D3
|
92
93
|
surplus_control: int | None = Field(None, alias="surplusControl")
|
93
94
|
surplus_standard: int | None = Field(None, alias="surplusStandard")
|
94
95
|
system_sound_enable: int | None = Field(None, alias="systemSoundEnable")
|
@@ -113,7 +114,7 @@ class FeedState(BaseModel):
|
|
113
114
|
eat_avg: int | None = Field(None, alias="eatAvg")
|
114
115
|
eat_count: int | None = Field(None, alias="eatCount")
|
115
116
|
eat_times: list[int] | None = Field(None, alias="eatTimes")
|
116
|
-
feed_times: dict | None = Field(None, alias="feedTimes")
|
117
|
+
feed_times: dict | list | None = Field(None, alias="feedTimes")
|
117
118
|
times: int | None = None
|
118
119
|
add_amount_total: int | None = Field(None, alias="addAmountTotal")
|
119
120
|
plan_amount_total: int | None = Field(None, alias="planAmountTotal")
|
@@ -135,17 +136,24 @@ class StateFeeder(BaseModel):
|
|
135
136
|
battery_power: int | None = Field(None, alias="batteryPower")
|
136
137
|
battery_status: int | None = Field(None, alias="batteryStatus")
|
137
138
|
bowl: int | None = None
|
139
|
+
block: int | None = None
|
140
|
+
broadcast: dict | None = None
|
138
141
|
camera_status: int | None = Field(None, alias="cameraStatus")
|
142
|
+
charge: int | None = None
|
139
143
|
desiccant_left_days: int | None = Field(None, alias="desiccantLeftDays")
|
140
144
|
desiccant_time: int | None = Field(None, alias="desiccantTime")
|
141
145
|
door: int | None = None
|
142
146
|
feed_state: FeedState | None = Field(None, alias="feedState")
|
143
147
|
feeding: int | None = None
|
148
|
+
error_code: str | None = Field(None, alias="errorCode")
|
149
|
+
error_detail: str | None = Field(None, alias="errorDetail")
|
150
|
+
error_level: int | None = Field(None, alias="errorLevel")
|
144
151
|
error_msg: str | None = Field(None, alias="errorMsg")
|
145
152
|
ota: int | None = None
|
146
153
|
overall: int | None = None
|
147
154
|
pim: int | None = None
|
148
155
|
runtime: int | None = None
|
156
|
+
weight: int | None = None
|
149
157
|
wifi: Wifi | None = None
|
150
158
|
eating: int | None = None
|
151
159
|
food: int | None = None
|
@@ -198,6 +206,7 @@ class RecordsItems(BaseModel):
|
|
198
206
|
eat_end_time: int | None = Field(None, alias="eatEndTime")
|
199
207
|
eat_start_time: int | None = Field(None, alias="eatStartTime")
|
200
208
|
eat_video: int | None = Field(None, alias="eatVideo")
|
209
|
+
eat_weight: int | None = Field(None, alias="eatWeight") # D3
|
201
210
|
empty: int | None = None
|
202
211
|
end_time: int | None = Field(None, alias="endTime")
|
203
212
|
enum_event_type: str | None = Field(None, alias="enumEventType")
|
@@ -210,10 +219,12 @@ class RecordsItems(BaseModel):
|
|
210
219
|
id: str | None = None
|
211
220
|
is_executed: int | None = Field(None, alias="isExecuted")
|
212
221
|
is_need_upload_video: int | None = Field(None, alias="isNeedUploadVideo")
|
222
|
+
left_weight: int | None = Field(None, alias="leftWeight") # D3
|
213
223
|
mark: int | None = None
|
214
224
|
media_api: str | None = Field(None, alias="mediaApi")
|
215
225
|
media_list: list[Any] | None = Field(None, alias="mediaList")
|
216
226
|
name: str | None = None
|
227
|
+
pet_id: str | None = Field(None, alias="petId")
|
217
228
|
preview: str | None = None
|
218
229
|
preview1: str | None = Field(None, alias="preview1")
|
219
230
|
preview2: str | None = Field(None, alias="preview2")
|
@@ -235,11 +246,13 @@ class RecordsType(BaseModel):
|
|
235
246
|
day: int | None = None
|
236
247
|
device_id: int | None = Field(None, alias="deviceId")
|
237
248
|
eat_count: int | None = Field(None, alias="eatCount")
|
249
|
+
eat_amount: int | None = Field(None, alias="eatAmount") # D3
|
238
250
|
items: list[RecordsItems] | None = None
|
239
251
|
plan_amount: int | None = Field(None, alias="planAmount")
|
240
252
|
real_amount: int | None = Field(None, alias="realAmount")
|
241
253
|
amount: int | None = None
|
242
254
|
times: int | None = None
|
255
|
+
user_id: str | None = Field(None, alias="userId") # D3
|
243
256
|
|
244
257
|
|
245
258
|
class FeederRecord(BaseModel):
|
@@ -254,8 +267,12 @@ class FeederRecord(BaseModel):
|
|
254
267
|
device_type: str | None = Field(None, alias="deviceType")
|
255
268
|
|
256
269
|
@classmethod
|
257
|
-
def get_endpoint(cls, device_type: str) -> str:
|
270
|
+
def get_endpoint(cls, device_type: str) -> str | None:
|
258
271
|
"""Get the endpoint URL for the given device type."""
|
272
|
+
if device_type == D3:
|
273
|
+
return PetkitEndpoint.DAILY_FEED_AND_EAT
|
274
|
+
if device_type == D4:
|
275
|
+
return PetkitEndpoint.FEED_STATISTIC
|
259
276
|
return PetkitEndpoint.GET_DEVICE_RECORD
|
260
277
|
|
261
278
|
@classmethod
|
@@ -268,7 +285,10 @@ class FeederRecord(BaseModel):
|
|
268
285
|
"""Generate query parameters including request_date."""
|
269
286
|
if request_date is None:
|
270
287
|
request_date = datetime.now().strftime("%Y%m%d")
|
271
|
-
|
288
|
+
|
289
|
+
if device.device_type.lower() == D4:
|
290
|
+
return {"date": request_date, "type": 0, "deviceId": device.device_id}
|
291
|
+
return {"days": request_date, "deviceId": device.device_id}
|
272
292
|
|
273
293
|
|
274
294
|
class Feeder(BaseModel):
|
@@ -281,7 +301,7 @@ class Feeder(BaseModel):
|
|
281
301
|
cloud_product: CloudProduct | None = Field(None, alias="cloudProduct")
|
282
302
|
created_at: str | None = Field(None, alias="createdAt")
|
283
303
|
firmware: float
|
284
|
-
firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
|
304
|
+
firmware_details: list[FirmwareDetail] | None = Field(None, alias="firmwareDetails")
|
285
305
|
hardware: int
|
286
306
|
id: int
|
287
307
|
locale: str | None = None
|
@@ -109,6 +109,9 @@ class StateLitter(BaseModel):
|
|
109
109
|
box_full: bool | None = Field(None, alias="boxFull")
|
110
110
|
box_state: int | None = Field(None, alias="boxState")
|
111
111
|
deodorant_left_days: int | None = Field(None, alias="deodorantLeftDays")
|
112
|
+
error_code: str | None = Field(None, alias="errorCode")
|
113
|
+
error_detail: str | None = Field(None, alias="errorDetail")
|
114
|
+
error_level: int | None = Field(None, alias="errorLevel")
|
112
115
|
error_msg: str | None = Field(None, alias="errorMsg")
|
113
116
|
frequent_restroom: int | None = Field(None, alias="frequentRestroom")
|
114
117
|
liquid_empty: bool | None = Field(None, alias="liquidEmpty")
|
@@ -41,28 +41,30 @@ async def extract_filename_from_url(url: str) -> str:
|
|
41
41
|
class MediaHandler:
|
42
42
|
"""Class to find media files from PetKit devices."""
|
43
43
|
|
44
|
-
def __init__(self,
|
44
|
+
def __init__(self, file_path: Path):
|
45
45
|
"""Initialize the class."""
|
46
|
-
self.device = device
|
47
46
|
self.media_download_decode = MediaDownloadDecode(file_path)
|
48
47
|
self.media_files: list[MediasFiles] = []
|
49
48
|
|
50
|
-
async def get_last_image(self) -> list[MediasFiles]:
|
49
|
+
async def get_last_image(self, device: Feeder) -> list[MediasFiles]:
|
51
50
|
"""Process device records and extract media info."""
|
52
51
|
record_types = ["eat", "feed", "move", "pet"]
|
53
52
|
self.media_files = []
|
54
53
|
|
55
|
-
if not
|
54
|
+
if not isinstance(device, Feeder):
|
55
|
+
_LOGGER.error("Device is not a Feeder")
|
56
|
+
return []
|
57
|
+
|
58
|
+
if not device.device_records:
|
56
59
|
_LOGGER.error("No device records found for feeder")
|
57
60
|
return []
|
58
61
|
|
59
62
|
for record_type in record_types:
|
60
|
-
records = getattr(
|
63
|
+
records = getattr(device.device_records, record_type, None)
|
61
64
|
if records:
|
62
65
|
self.media_files.extend(
|
63
66
|
await self._process_records(records, record_type)
|
64
67
|
)
|
65
|
-
|
66
68
|
return self.media_files
|
67
69
|
|
68
70
|
async def _process_records(
|
@@ -111,45 +113,39 @@ class MediaHandler:
|
|
111
113
|
class MediaDownloadDecode:
|
112
114
|
"""Class to download"""
|
113
115
|
|
114
|
-
def __init__(self, download_path:
|
116
|
+
def __init__(self, download_path: Path):
|
115
117
|
"""Initialize the class."""
|
116
118
|
self.download_path = download_path
|
117
119
|
|
118
120
|
async def get_file(self, url: str, aes_key: str) -> bool:
|
119
121
|
"""Download a file from a URL and decrypt it."""
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
122
|
+
# Check if the file already exists
|
123
|
+
filename = await extract_filename_from_url(url)
|
124
|
+
full_file_path = Path(self.download_path) / filename
|
125
|
+
if full_file_path.exists():
|
126
|
+
_LOGGER.debug("File already exist : %s don't need to download it", filename)
|
127
|
+
return True
|
128
|
+
|
129
|
+
# Download the file
|
130
|
+
async with aiohttp.ClientSession() as session, session.get(url) as response:
|
131
|
+
if response.status != 200:
|
132
|
+
_LOGGER.error(
|
133
|
+
"Failed to download %s, status code: %s", url, response.status
|
127
134
|
)
|
128
|
-
return
|
135
|
+
return False
|
129
136
|
|
130
|
-
|
131
|
-
async with aiohttp.ClientSession() as session, session.get(url) as response:
|
132
|
-
if response.status != 200:
|
133
|
-
_LOGGER.error(
|
134
|
-
"Failed to download %s, status code: %s", url, response.status
|
135
|
-
)
|
136
|
-
return False
|
137
|
+
content = await response.read()
|
137
138
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
)
|
139
|
+
encrypted_file_path = await self._save_file(content, f"{filename}.enc")
|
140
|
+
# Decrypt the image
|
141
|
+
decrypted_data = await self._decrypt_image_from_file(
|
142
|
+
encrypted_file_path, aes_key
|
143
|
+
)
|
144
144
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
return True
|
150
|
-
_LOGGER.error("Failed to decrypt %s", encrypted_file_path)
|
151
|
-
except Exception as e: # noqa: BLE001
|
152
|
-
_LOGGER.error("Error get media file from %s: %s", url, e)
|
145
|
+
if decrypted_data:
|
146
|
+
_LOGGER.debug("Decrypt was successful")
|
147
|
+
await self._save_file(decrypted_data, filename)
|
148
|
+
return True
|
153
149
|
return False
|
154
150
|
|
155
151
|
async def _save_file(self, content: bytes, filename: str) -> Path:
|
@@ -198,4 +194,6 @@ class MediaDownloadDecode:
|
|
198
194
|
except Exception as e: # noqa: BLE001
|
199
195
|
logging.error("Error decrypting image from file %s: %s", file_path, e)
|
200
196
|
return None
|
197
|
+
if Path(file_path).exists():
|
198
|
+
Path(file_path).unlink()
|
201
199
|
return decrypted_data
|
@@ -187,7 +187,7 @@ build-backend = "poetry.core.masonry.api"
|
|
187
187
|
|
188
188
|
[tool.poetry]
|
189
189
|
name = "pypetkitapi"
|
190
|
-
version = "1.7.
|
190
|
+
version = "1.7.7"
|
191
191
|
description = "Python client for PetKit API"
|
192
192
|
authors = ["Jezza34000 <info@mail.com>"]
|
193
193
|
readme = "README.md"
|
@@ -199,7 +199,7 @@ python = ">=3.11"
|
|
199
199
|
aiohttp = "^3.10.10"
|
200
200
|
aiofiles = "^24.1.0"
|
201
201
|
pycryptodome = "^3.19.1"
|
202
|
-
pydantic = "^1.10.
|
202
|
+
pydantic = "^1.10.18"
|
203
203
|
|
204
204
|
[tool.poetry.dev-dependencies]
|
205
205
|
pre-commit = "^4.0.1"
|
@@ -208,7 +208,7 @@ ruff = "^0.8.1"
|
|
208
208
|
types-aiofiles = "^24.1.0.20240626"
|
209
209
|
|
210
210
|
[tool.bumpver]
|
211
|
-
current_version = "1.7.
|
211
|
+
current_version = "1.7.7"
|
212
212
|
version_pattern = "MAJOR.MINOR.PATCH"
|
213
213
|
commit_message = "bump version {old_version} -> {new_version}"
|
214
214
|
tag_message = "{new_version}"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|