Skip to content

Commit b1ada3c

Browse files
authored
Merge pull request #6 from sov2000/image-ftr-1
v1.0.2 commit - adding new commands and some bug fixes
2 parents 6cfe3b3 + 2e06323 commit b1ada3c

21 files changed

+457
-16
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Change Log
22

3+
## [1.0.2] - 2024-12
4+
5+
### Added
6+
- User command to display authenticated user and other user info by ID
7+
- Shop command to pull info about a shop by its IP, user ID, and search by name
8+
- Image command with full support for get, upload, update, and delete
9+
- Listing Property command with support to retrieve, update, and delete values
10+
11+
### Updated
12+
- Minor bug fixes and documentation updates
13+
314
## [1.0.1] - 2024-12
415

516
### Added

README.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,46 @@ Before you can use the app, you will need to obtain an API key from Etsy. You ca
6262
- E.g. `ETSPI_KEY=YOU_ETSY_APP_KEYSTRING` or `ETSPI_TOKEN=YOUR_AUTH_TOKEN`.
6363
- Command line options take precedence over the env variables and variables take precedence over the values stored in the `Auth.env` file.
6464

65+
## User:
66+
67+
Retrieve current authenticated user ID and Shop ID.
68+
69+
```bash
70+
etspi user
71+
```
72+
73+
More User details can be retrieved for a specific User ID.
74+
75+
```bash
76+
etspi user -u 123456789
77+
```
78+
79+
## Shop:
80+
81+
Retrieve detailed shop info by Shop Id.
82+
83+
```bash
84+
etspi shop-get -s 12345678
85+
```
86+
87+
Same as before but pull info by User ID instead.
88+
89+
```bash
90+
etspi shop-get -u 123456789
91+
```
92+
93+
Alternatively, search by Shop Name performing typical partial name search.
94+
95+
```bash
96+
etspi shop-get -n Paint
97+
```
98+
99+
Since you the number of the results can be quite large, `-R` option can be used to limit and offset the results with the first number as the `offset` and the second number as the `limit` to retrieve.
100+
101+
```bash
102+
etspi shop-get -n Paint -R 9 1
103+
```
104+
65105
## Listing:
66106

67107
### listing-get
@@ -106,7 +146,7 @@ etspi listing-update -i 1800000081 -s 10000001 -f my_listing_update.json
106146
107147
### listing-delete
108148
109-
This is self-explanatory and will delete a listing by Id. Use `-Y` or `--yes` flat to suppress ***confirmation prompt*** before making the API call to delete the listing!
149+
This is self-explanatory and will delete a listing by Id. Use `-Y` or `--yes` flag to suppress ***confirmation prompt*** before making the API call to delete the listing!
110150
111151
```bash
112152
etspi listing-delete -i 1800000081 -Y
@@ -140,6 +180,74 @@ Use to update the listing inventory with the values from a JSON source file spec
140180
etspi listing-update-iv -i 1800000081 -f my_listing_update.json
141181
```
142182
183+
## Images:
184+
185+
### image-get
186+
187+
Display all listing Images with IDs, URLs, and other metadata.
188+
189+
```bash
190+
etspi image-get -i 1800000081
191+
```
192+
193+
Or if information is only needed for one image, pull it by Image ID in addition to the listing ID.
194+
195+
```bash
196+
etspi image-get -i 1800000081 -ii 6123123123
197+
```
198+
### image-upload
199+
200+
Upload a new image and add it to the listing at the specified `rank` with option `-r` and `-a` for alternate text. If you are uploading into existing `rank`, you will need `-O` option to allow overwriting of the content.
201+
202+
```bash
203+
etspi image-upload -i 1800000081 -s 12345678 -f "path\to\my\Listing_Image_File.jpg" -r 5 -a "Tree print wall art"
204+
```
205+
206+
### image-delete
207+
208+
Delete an image from listing by Image Id. Use `-Y` or `--yes` flag to suppress ***confirmation prompt*** before making the API call to delete.
209+
210+
```bash
211+
etspi image-delete -i 1800000081 -ii 6123123123
212+
```
213+
214+
## Properties:
215+
216+
### prop-get:
217+
218+
To retrieve all properties for a listing.
219+
220+
```bash
221+
etspi prop-get -i 1800000081 -s 12345678
222+
```
223+
224+
To retrieve a specific property (with ID 98765432) for a listing
225+
226+
```bash
227+
etspi prop-get -i 1800000081 -s 12345678 -pi 98765432
228+
```
229+
230+
To retrieve all properties for a listing and save the output to a file named "listing_properties.json"
231+
232+
```bash
233+
etspi prop-get -i 1800000081 -s 12345678 -o listing_properties.json
234+
```
235+
236+
To retrieve all properties for a listing, format the output for a listing property update request, and save the output to a file named "listing_property_update.json"
237+
238+
```bash
239+
etspi prop-get -i 1800000081 -s 12345678 --format-update -o listing_property_update.json
240+
```
241+
242+
### prop-update
243+
244+
To update a property for listing, you will need a JSON file in the correct update format. Use the `prop-get` command to generte a file you can modify in the correct format.
245+
246+
247+
```bash
248+
etspi prop-update -i 1800000081 -s 12345678 -pi 98765432 -f listing_property_update.json
249+
```
250+
143251
## Note on STDOUT redirects
144252
145253
Etspi console output uses `rich` library `print` which will color code and format JSON for more esthetic and readable output. When using Etspi `--out` you get original formatted JSON which is what you want for subsequent commands and API calls. If you have a need to pipe or redirect Etspi output to a different console app or file, you may want this original JSON and not pretty formatted. In that case, consider using `--out` to STDOUT `-` with `-S` and apply your pipe or redirect this way.

etspi/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import click
1212

13-
__version__ = "1.0.1"
13+
__version__ = "1.0.2"
1414

1515
# Click context and environment utility class
1616
class Environment:
@@ -136,6 +136,7 @@ def show_auth_params(root: click.Context, ctx: Any) -> None:
136136
@click.option("-E", "--expiry", required=False, help="API access token future expiration timestamp.")
137137
@click.option("-nP", "--no-persist", is_flag=True, default=False, help="Do not update auth.env file when tokens are refreshed. By default, if the file exists, new tokens are automatically saved.")
138138
@click.option("-v", "--verbose", is_flag=True, help="Enables verbose output mode.")
139+
@click.version_option(version=__version__, message='%(prog)s, version %(version)s')
139140
@pass_environment
140141
@click.pass_context
141142
def cli(root: Any, ctx: Any, token: Any, refresh_token: Any, key: Any, expiry: Any, no_persist: bool, verbose: Any) -> None:

etspi/commands/cmd_auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def start_httpd_service(ctx: Any, host: str, port: int, certfile: str, keyfile:
110110

111111
def check_all_scopes(scopes: List) -> List:
112112
if "all" in scopes:
113-
return ["feedback_r", "listings_d", "listings_r", "listings_w", "shops_r", "shops_w", "transactions_r", "transactions_w"]
113+
return ["email_r", "listings_d", "listings_r", "listings_w", "shops_r", "shops_w", "transactions_r", "transactions_w"]
114114
return scopes
115115

116116
def get_auth_directions(ctx: Any, auth_hlp: AuthHelper, rdr_url: str, scope_list: list) -> None:
@@ -136,7 +136,7 @@ def get_auth_directions(ctx: Any, auth_hlp: AuthHelper, rdr_url: str, scope_list
136136
default=os.path.join(os.path.expanduser('~'), ".etspi\\key.pem"),
137137
help="Callback server SSL key file location.")
138138
@click.option("-s", "--scope", multiple=True, default=["all"],
139-
type=click.Choice(["all", "feedback_r", "listings_d", "listings_r", "listings_w", "shops_r", "shops_w", "transactions_r", "transactions_w"], case_sensitive=True),
139+
type=click.Choice(["all", "email_r", "listings_d", "listings_r", "listings_w", "shops_r", "shops_w", "transactions_r", "transactions_w"], case_sensitive=True),
140140
help="Specify scope options to request for authorization or 'all' for all currently supported scopes.")
141141
@click.option("-tF", "--token-file", is_flag=True, default=False, help="Write out the tokens to the auth.env file.")
142142
@click.option("-tH", "--token-hidden", is_flag=True, default=False, help="Supress the output of the tokens to the console.")

etspi/commands/cmd_draft.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from etspi.etsyv3 import EtsyAPI, Includes
1010
from etspi.etsyv3.models import CreateDraftListingRequest
1111

12-
def draft_listing(ctx: Any, shop_id: str, src_file: Any, silent: bool) -> None:
12+
def draft_listing(ctx: Any, shop_id: int, src_file: Any, silent: bool) -> None:
1313
pld = json.load(src_file)
1414
etsy = ctx.get_etsy("DRAFT")
1515
if "type" in pld:

etspi/commands/cmd_image-delete.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import click
2+
3+
from typing import Any, Dict, List, Optional
4+
from rich import print
5+
from etspi.cli import pass_environment, Environment
6+
from etspi.etsyv3 import EtsyAPI, Includes
7+
8+
def delete_listing_image(ctx: Any, id: int, shop_id: int, img_id: int, yes: bool, silent: bool) -> None:
9+
ctx.vlog(f"Delete Action for Listing ID: {id} and Image ID: {img_id}")
10+
etsy = ctx.get_etsy("IMAGE-DELETE")
11+
confirm = True
12+
if not yes:
13+
user_input = input(f"Are you sure you want to delete image {img_id} from listing {id}? (yes/no):")
14+
if user_input.lower() not in ['yes', 'y']:
15+
confirm = False
16+
if confirm:
17+
ctx.vlog(f"Deleting Image ID: {img_id} from Listing ID: {id}")
18+
res = etsy.delete_listing_image(shop_id, id, img_id)
19+
if not silent:
20+
print(res)
21+
else:
22+
ctx.vlog(f"Abort Delete Image ID: {img_id} from Listing ID: {id}")
23+
return
24+
25+
@click.command("image-delete", short_help="Delete listing image by Image and Listing ID")
26+
@click.option("-i", "--id", required=True, type=click.INT, help="Listing ID to which apply action.")
27+
@click.option("-s", "--shop-id", required=False, type=click.INT, help="Shop ID to use for update and other actions.")
28+
@click.option("-ii", "--img-id", required=True, type=click.INT, help="Image ID to delete.")
29+
@click.option("-Y", "--yes", required=False, default=False, is_flag=True, help="Do not ask to confirm image delete.")
30+
@click.option("-S", "--silent", required=False, default=False, is_flag=True, help="Supress console output.")
31+
@pass_environment
32+
def cli(ctx, id, shop_id, img_id, yes, silent):
33+
"""Perform a Delete action on a listing by Listing and Image IDs."""
34+
ctx.check_auth("IMAGE-DELETE")
35+
if not id is None:
36+
ctx.vlog(f"Process DELETE IMAGE listing: {id}")
37+
try:
38+
delete_listing_image(ctx, id, shop_id, img_id, yes, silent)
39+
except Exception as ex:
40+
ctx.log(f"Error processing command - {ex}")

etspi/commands/cmd_image-get.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
import click
3+
import jmespath
4+
5+
from typing import Any, Dict, List, Optional
6+
from rich import print
7+
from etspi.cli import pass_environment, Environment
8+
from etspi.etsyv3 import EtsyAPI
9+
10+
def get_listing_image(ctx: Any, id: int, img_id: int, query: str, out: Any, silent: bool) -> None:
11+
ctx.vlog(f"Get Image Action for Listing ID: {id} Image ID: {img_id}")
12+
etsy = ctx.get_etsy("LISTING-GET")
13+
if img_id > 0:
14+
res = etsy.get_listing_image(id, img_id)
15+
else:
16+
res = etsy.get_listing_images(id)
17+
if res:
18+
if query:
19+
res = jmespath.search(query, res)
20+
if out:
21+
out.write(json.dumps(res, indent=4))
22+
if not silent:
23+
print(res)
24+
return
25+
26+
@click.command("image-get", short_help="Retrieve or Get listing image(s) by Image and Listing ID")
27+
@click.option("-i", "--id", required=True, type=click.INT, help="Listing ID from which to retrieve the image.")
28+
@click.option("-ii", "--img-id", required=False, default=0, type=click.INT, help="Image ID to retrieve or, if omitted or 0, get all images.")
29+
@click.option("-q", "--query", type=click.STRING, help="JMESPath query to filter the output of the command.")
30+
@click.option("-o", "--out", required=False, type=click.File(mode="w", encoding="utf-8", errors="strict", lazy=None, atomic=False), help="Also output result into a file.")
31+
@click.option("-S", "--silent", required=False, default=False, is_flag=True, help="Supress console output.")
32+
@pass_environment
33+
def cli(ctx, id, img_id, query, out, silent):
34+
"""Perform a GET IMAGE action on a listing by Listing ID and optional Image ID."""
35+
ctx.check_auth("IMAGE-GET")
36+
if not id is None:
37+
ctx.vlog(f"Process GET Image Listing ID: {id} Image ID: {img_id}")
38+
try:
39+
get_listing_image(ctx, id, img_id, query, out, silent)
40+
except Exception as ex:
41+
ctx.log(f"Error processing command - {ex}")

etspi/commands/cmd_image-upload.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import click
2+
3+
from typing import Any, Dict, List, Optional
4+
from rich import print
5+
from etspi.cli import pass_environment, Environment
6+
from etspi.etsyv3 import EtsyAPI, Includes
7+
from etspi.etsyv3.models import UploadListingImageRequest, UpdateListingImageIDRequest
8+
9+
def upload_listing_image(ctx: Any, id: int, shop_id: int, src_file: Any, img_id: int, rank: int,
10+
overwrite: bool, watermark: bool, alt_text: str, silent: bool) -> None:
11+
ctx.vlog(f"Upload or Update Image Action for Listing ID: {id} Image ID {img_id}")
12+
etsy = ctx.get_etsy("LISTING-UPDATE")
13+
if not src_file is None:
14+
img_content = src_file.read()
15+
listing_img = UploadListingImageRequest(image_bytes = img_content, listing_image_id = img_id, rank = rank, overwrite = overwrite,
16+
is_watermarked = watermark, alt_text = alt_text)
17+
res = etsy.upload_listing_image(shop_id, id, listing_img)
18+
elif not img_id is None:
19+
listing_img = UpdateListingImageIDRequest(listing_image_id = img_id, rank = rank, overwrite = overwrite,
20+
is_watermarked = watermark, alt_text = alt_text)
21+
res = etsy.update_listing_image_id(shop_id, id, listing_img)
22+
else:
23+
raise click.BadArgumentUsage("No Source File or Image Id provided for upload or update.")
24+
if not silent:
25+
print(res)
26+
return
27+
28+
@click.command("image-upload", short_help="Upload a new listing image or update by Image ID")
29+
@click.option("-i", "--id", required=True, type=click.INT, help="Listing ID to which apply action.")
30+
@click.option("-s", "--shop-id", required=False, type=click.INT, help="Shop ID to use for update and other actions.")
31+
@click.option("-f", "--src-file", required=False, help="Source image file from which to read content to upload.",
32+
type=click.File(mode="rb", encoding="utf-8", errors="strict", lazy=None, atomic=False))
33+
@click.option("-ii", "--img-id", required=False, default=1, type=click.INT, help="Image ID to update for existing.")
34+
@click.option("-r", "--rank", required=False, default=1, type=click.INT, help="Image rank to upload or update.")
35+
@click.option("-O", "--overwrite", required=False, default=False, is_flag=True, help="Overwrite file flag to replace existing image.")
36+
@click.option("-W", "--watermark", required=False, default=False, is_flag=True, help="Set to indicate image has a watermark.")
37+
@click.option("-a", "--alt-text", required=False, default="", type=click.STRING, help="Image ALT Text to set upon upload or update.")
38+
@click.option("-S", "--silent", required=False, default=False, is_flag=True, help="Supress console output.")
39+
@pass_environment
40+
def cli(ctx, id, shop_id, src_file, img_id, rank, overwrite, watermark, alt_text, silent):
41+
"""Upload a new image or Update existing image by Listing ID and Image ID."""
42+
ctx.check_auth("IMAGE-UPLOAD")
43+
if not id is None:
44+
ctx.vlog(f"Process Upload or Update Image Listing ID: {id} Image ID: {img_id}")
45+
try:
46+
upload_listing_image(ctx, id, shop_id, src_file, img_id, rank, overwrite, watermark, alt_text, silent)
47+
except Exception as ex:
48+
ctx.log(f"Error processing command - {ex}")

etspi/commands/cmd_listing-delete.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from etspi.cli import pass_environment, Environment
66
from etspi.etsyv3 import EtsyAPI, Includes
77

8-
def delete_listing(ctx: Any, id: str, yes: bool, silent: bool) -> None:
8+
def delete_listing(ctx: Any, id: int, yes: bool, silent: bool) -> None:
99
ctx.vlog(f"Delete Action for Listing ID: {id}")
1010
etsy = ctx.get_etsy("LISTING-DELETE")
1111
confirm = True

etspi/commands/cmd_listing-get-iv.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from etspi.etsyv3 import EtsyAPI, Includes
99
from etspi.etsyv3.models import UpdateListingInventoryRequest
1010

11-
def get_listing_inventory(ctx: Any, id: str, transform: str, query: str, out: Any, silent: bool) -> None:
11+
def get_listing_inventory(ctx: Any, id: int, transform: str, query: str, out: Any, silent: bool) -> None:
1212
ctx.vlog(f"Get Inventroy Action for Listing ID: {id}")
1313
etsy = ctx.get_etsy("LISTING-GET-IV")
1414
res = etsy.get_listing_inventory(id)
@@ -25,7 +25,6 @@ def get_listing_inventory(ctx: Any, id: str, transform: str, query: str, out: An
2525

2626
@click.command("listing-get-iv", short_help="Retrieve or Get a listing inventory by ID")
2727
@click.option("-i", "--id", required=True, type=click.INT, help="Listing ID to which apply action.")
28-
#@click.option("-t", "--transform", is_flag=True, default=False, help="Transform listing or inventory response into format suitable for update inventory request.")
2928
@click.option("--format-update", "transform", flag_value="update", default=False, help="Transform listing response into format suitable for listing update request.")
3029
@click.option("-q", "--query", type=click.STRING, help="JMESPath query to filter the output of the command.")
3130
@click.option("-o", "--out", required=False, type=click.File(mode="w", encoding="utf-8", errors="strict", lazy=None, atomic=False), help="Also output result into a file")

0 commit comments

Comments
 (0)