Coverage for amazonorders/cli.py: 81.15%

122 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-24 18:41 +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.4" 

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 

186def _print_banner(): 

187 click.echo(""" 

188======================================================================= 

189 ___ _____ _  

190 / _ \ | _ | | |  

191/ /_\ \_ __ ___ __ _ _______ _ __ | | | |_ __ __| | ___ _ __ ___  

192| _ | '_ ` _ \ / _` |_ / _ \| '_ \ | | | | '__/ _` |/ _ \ '__/ __| 

193| | | | | | | | | (_| |/ / (_) | | | | \ \_/ / | | (_| | __/ | \__ \\ 

194\_| |_/_| |_| |_|\__,_/___\___/|_| |_| \___/|_| \__,_|\___|_| |___/  

195=======================================================================\n""") 

196 

197 

198def _order_output(order): 

199 order_str = """----------------------------------------------------------------------- 

200Order #{} 

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

202 

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

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

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

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

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

208 if order.payment_method: 

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

210 if order.payment_method_last_4: 

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

212 if order.subtotal: 

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

214 if order.shipping_total: 

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

216 if order.subscription_discount: 

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

218 if order.total_before_tax: 

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

220 if order.estimated_tax: 

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

222 if order.refund_total: 

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

224 if order.order_shipped_date: 

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

226 if order.refund_completed_date: 

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

228 

229 order_str += "\n-----------------------------------------------------------------------" 

230 

231 return order_str 

232 

233 

234if __name__ == "__main__": 

235 amazon_orders_cli(obj={})