Coverage for amazonorders/cli.py: 80.95%

126 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-25 22:50 +0000

1import datetime 

2import logging 

3from typing import Any 

4 

5import click 

6from click.core import Context 

7 

8from amazonorders.conf import DEFAULT_OUTPUT_DIR 

9from amazonorders.exception import AmazonOrdersError 

10from amazonorders.orders import AmazonOrders 

11from amazonorders.session import AmazonSession, IODefault 

12 

13__author__ = "Alex Laird" 

14__copyright__ = "Copyright 2024, Alex Laird" 

15__version__ = "1.0.6" 

16 

17logger = logging.getLogger("amazonorders") 

18 

19 

20class IOClick(IODefault): 

21 def echo(self, 

22 msg, 

23 fg=None, 

24 **kwargs): 

25 click.secho(msg, fg=fg) 

26 

27 def prompt(self, 

28 msg, 

29 type=None, 

30 **kwargs): 

31 return click.prompt(msg, type=type) 

32 

33 

34@click.group() 

35@click.option('--username', help="An Amazon username.") 

36@click.option('--password', help="An Amazon password.") 

37@click.option('--debug', is_flag=True, default=False, help="Enable debugging and send output to command line.") 

38@click.option('--max-auth-attempts', default=10, 

39 help="Will continue in the login auth loop this many times (successes and failures).") 

40@click.option('--output-dir', default=DEFAULT_OUTPUT_DIR, 

41 help="The directory where any output files should be produced.") 

42@click.pass_context 

43def amazon_orders_cli(ctx, 

44 **kwargs: Any): 

45 """ 

46 amazon-orders is an unofficial library that provides a command line interface alongside a programmatic API that 

47 can be used to interact with Amazon.com's consumer-facing website. 

48 

49 This works by parsing website data from Amazon.com. A nightly build validates functionality to ensure its 

50 stability, but as Amazon provides no official API to use, this package may break at any time. This 

51 package only supports the English version of the website. 

52 

53 Documentation can be found at https://amazon-orders.readthedocs.io. 

54 

55 Session data is persisted between requests and interactions with the CLI, minimizing the need to reauthenticate 

56 after a successful login attempt. 

57 """ 

58 _print_banner() 

59 

60 ctx.ensure_object(dict) 

61 for key, value in kwargs.items(): 

62 if value: 

63 ctx.obj[key] = value 

64 

65 if kwargs["debug"]: 

66 logger.setLevel(logging.DEBUG) 

67 logger.addHandler(logging.StreamHandler()) 

68 

69 username = kwargs.get("username") 

70 password = kwargs.get("password") 

71 

72 amazon_session = AmazonSession(username, 

73 password, 

74 debug=kwargs["debug"], 

75 io=IOClick(), 

76 max_auth_attempts=kwargs["max_auth_attempts"], 

77 output_dir=kwargs["output_dir"]) 

78 

79 if amazon_session.auth_cookies_stored(): 

80 if username or password: 

81 click.echo("Info: You've provided --username and --password, but because a previous session still exists," 

82 "that is being ignored. If you would like to reauthenticate, call the `logout` command first.\n") 

83 elif not username and not password: 

84 click.echo(ctx.get_help()) 

85 

86 ctx.fail("Amazon --username and --password must be provided, since no previous session was found.") 

87 

88 ctx.obj["amazon_session"] = amazon_session 

89 

90 

91@amazon_orders_cli.command() 

92@click.pass_context 

93@click.option('--year', default=datetime.date.today().year, 

94 help="The year for which to get order history, defaults to the current year.") 

95@click.option('--start-index', help="Retrieve the single page of history at the given index.") 

96@click.option('--full-details', is_flag=True, default=False, 

97 help="Retrieve the full details for each order in the history.") 

98def history(ctx: Context, 

99 **kwargs: Any): 

100 """ 

101 Retrieve Amazon order history for a given year. 

102 """ 

103 amazon_session = ctx.obj["amazon_session"] 

104 

105 year = kwargs["year"] 

106 start_index = kwargs["start_index"] 

107 full_details = kwargs["full_details"] 

108 

109 click.echo("""----------------------------------------------------------------------- 

110Order History for {}{}{} 

111-----------------------------------------------------------------------\n""".format(year, 

112 ", startIndex={}, one page".format( 

113 start_index) if start_index else ", all pages", 

114 ", with full details" if full_details else "")) 

115 

116 click.echo("Info: This might take a minute ...\n") 

117 

118 try: 

119 amazon_session.login() 

120 

121 amazon_orders = AmazonOrders(amazon_session, 

122 debug=amazon_session.debug, 

123 output_dir=ctx.obj["output_dir"]) 

124 

125 orders = amazon_orders.get_order_history(year=kwargs["year"], 

126 start_index=kwargs["start_index"], 

127 full_details=kwargs["full_details"], ) 

128 

129 for order in orders: 

130 click.echo("{}\n".format(_order_output(order))) 

131 except AmazonOrdersError as e: 

132 logger.debug("An error occurred.", exc_info=True) 

133 ctx.fail(str(e)) 

134 

135 

136@amazon_orders_cli.command() 

137@click.pass_context 

138@click.argument("order_id") 

139def order(ctx: Context, 

140 order_id: str): 

141 """ 

142 Retrieve the full details for the given Amazon order ID. 

143 """ 

144 amazon_session = ctx.obj["amazon_session"] 

145 

146 try: 

147 amazon_session.login() 

148 

149 amazon_orders = AmazonOrders(amazon_session, 

150 debug=amazon_session.debug, 

151 output_dir=ctx.obj["output_dir"]) 

152 

153 order = amazon_orders.get_order(order_id) 

154 

155 click.echo("{}\n".format(_order_output(order))) 

156 except AmazonOrdersError as e: 

157 logger.debug("An error occurred.", exc_info=True) 

158 ctx.fail(str(e)) 

159 

160 

161@amazon_orders_cli.command(short_help="Check if persisted session exists.") 

162@click.pass_context 

163def check_session(ctx: Context): 

164 """ 

165 Check if a persisted session exists, meaning commands can be called without needing to provide credentials. 

166 """ 

167 amazon_session = ctx.obj["amazon_session"] 

168 if amazon_session.auth_cookies_stored(): 

169 click.echo("Info: A persisted session exists.\n") 

170 else: 

171 click.echo("Info: No persisted session exists.\n") 

172 

173 

174@amazon_orders_cli.command() 

175@click.pass_context 

176def logout(ctx: Context): 

177 """ 

178 Logout of existing Amazon sessions and clear cookies. 

179 """ 

180 amazon_session = ctx.obj["amazon_session"] 

181 amazon_session.logout() 

182 

183 click.echo("Info: Successfully logged out of the Amazon session.\n") 

184 

185 

186@amazon_orders_cli.command() 

187@click.pass_context 

188def version(ctx: Context): 

189 """ 

190 Get the package version. 

191 """ 

192 click.echo("Version: {}\n".format(__version__)) 

193 

194 

195def _print_banner(): 

196 click.echo(""" 

197======================================================================= 

198 ___ _____ _  

199 / _ \ | _ | | |  

200/ /_\ \_ __ ___ __ _ _______ _ __ | | | |_ __ __| | ___ _ __ ___  

201| _ | '_ ` _ \ / _` |_ / _ \| '_ \ | | | | '__/ _` |/ _ \ '__/ __| 

202| | | | | | | | | (_| |/ / (_) | | | | \ \_/ / | | (_| | __/ | \__ \\ 

203\_| |_/_| |_| |_|\__,_/___\___/|_| |_| \___/|_| \__,_|\___|_| |___/  

204=======================================================================\n""") 

205 

206 

207def _order_output(order): 

208 order_str = """----------------------------------------------------------------------- 

209Order #{} 

210-----------------------------------------------------------------------""".format(order.order_number) 

211 

212 order_str += "\n Shipments: {}".format(order.shipments) 

213 order_str += "\n Order Details Link: {}".format(order.order_details_link) 

214 order_str += "\n Grand Total: ${:,.2f}".format(order.grand_total) 

215 order_str += "\n Order Placed Date: {}".format(order.order_placed_date) 

216 order_str += "\n {}".format(order.recipient) 

217 if order.payment_method: 

218 order_str += "\n Payment Method: {}".format(order.payment_method) 

219 if order.payment_method_last_4: 

220 order_str += "\n Payment Method Last 4: {}".format(order.payment_method_last_4) 

221 if order.subtotal: 

222 order_str += "\n Subtotal: ${:,.2f}".format(order.subtotal) 

223 if order.shipping_total: 

224 order_str += "\n Shipping Total: ${:,.2f}".format(order.shipping_total) 

225 if order.subscription_discount: 

226 order_str += "\n Subscription Discount: ${:,.2f}".format(order.subscription_discount) 

227 if order.total_before_tax: 

228 order_str += "\n Total Before Tax: ${:,.2f}".format(order.total_before_tax) 

229 if order.estimated_tax: 

230 order_str += "\n Estimated Tax: ${:,.2f}".format(order.estimated_tax) 

231 if order.refund_total: 

232 order_str += "\n Refund Total: ${:,.2f}".format(order.refund_total) 

233 if order.order_shipped_date: 

234 order_str += "\n Order Shipped Date: {}".format(order.order_shipped_date) 

235 if order.refund_completed_date: 

236 order_str += "\n Refund Completed Date: {}".format(order.refund_completed_date) 

237 

238 order_str += "\n-----------------------------------------------------------------------" 

239 

240 return order_str 

241 

242 

243if __name__ == "__main__": 

244 amazon_orders_cli(obj={})