Amazon-MWS
view release on metacpan or search on metacpan
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
use utf8;
use strict;
use warnings;
use DBI;
use Amazon::MWS::XML::Feed;
use Amazon::MWS::XML::Order;
use Amazon::MWS::Client;
use Amazon::MWS::XML::Response::FeedSubmissionResult;
use Amazon::MWS::XML::Response::OrderReport;
use Data::Dumper;
use File::Spec;
use DateTime;
use SQL::Abstract;
use Try::Tiny;
use Path::Tiny;
use Scalar::Util qw/blessed/;
use XML::Compile::Schema;
use Moo;
use MooX::Types::MooseLike::Base qw(:all);
use namespace::clean;
our $VERSION = '0.18';
use constant {
AMW_ORDER_WILDCARD_ERROR => 999999,
DEBUG => $ENV{AMZ_UPLOADER_DEBUG},
};
=head1 NAME
Amazon::MWS::Uploader -- high level agent to upload products to AMWS
=head1 DESCRIPTION
This module provide an high level interface to the upload process. It
has to keep track of the state to resume the uploading, which could
get stuck on the Amazon's side processing, so database credentials
have to be provided (or the database handle itself).
The table structure needed is defined and commented in sql/amazon.sql
=head1 SYNOPSIS
my $agent = Amazon::MWS::Uploader->new(
db_dsn => 'DBI:mysql:database=XXX',
db_username => 'xxx',
db_password => 'xxx',
db_options => \%options
# or dbh => $dbh,
schema_dir => '/path/to/xml_schema',
feed_dir => '/path/to/directory/for/xml',
merchant_id => 'xxx',
access_key_id => 'xxx',
secret_key => 'xxx',
marketplace_id => 'xxx',
endpoint => 'xxx',
products => \@products,
);
# say once a day, retrieve the full batch and send it up
$agent->upload;
# every 10 minutes or so, continue the work started with ->upload, if any
$agent->resume;
=head1 UPGRADE NOTES
When migrating from 0.05 to 0.06 please execute this SQL statement
ALTER TABLE amazon_mws_products ADD COLUMN listed BOOLEAN;
UPDATE amazon_mws_products SET listed = 1 WHERE status = 'ok';
When upgrading to 0.16, please execute this SQL statement:
ALTER TABLE amazon_mws_products ADD COLUMN warnings TEXT;
=head1 ACCESSORS
The following keys must be passed at the constructor and can be
accessed read-only:
=over 4
=item dbh
The DBI handle. If not provided will be built using the following
self-describing accessor:
=item db_dsn
=item db_username
=item db_password
=item db_options
E.g.
{
mysql_enable_utf8 => 1,
}
AutoCommit and RaiseError are set by us.
=cut
has db_dsn => (is => 'ro');
has db_password => (is => 'ro');
has db_username => (is => 'ro');
has db_options => (is => 'ro',
isa => AnyOf[Undef,HashRef],
);
has dbh => (is => 'lazy');
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
isa => sub {
die "$_[0] is not a directory" unless -d $_[0];
});
has schema => (is => 'lazy');
sub _build_schema {
my $self = shift;
my $files = File::Spec->catfile($self->schema_dir, '*.xsd');
my $schema = XML::Compile::Schema->new([glob $files]);
return $schema;
}
has xml_writer => (is => 'lazy');
sub _build_xml_writer {
my $self = shift;
return $self->schema->compile(WRITER => 'AmazonEnvelope');
}
has xml_reader => (is => 'lazy');
sub _build_xml_reader {
my $self = shift;
return $self->schema->compile(READER => 'AmazonEnvelope');
}
=item generic_feeder
Return a L<Amazon::MWS::XML::GenericFeed> object to build a feed using
the XML writer.
=cut
sub generic_feeder {
my $self = shift;
return Amazon::MWS::XML::GenericFeed->new(
xml_writer => $self->xml_writer,
merchant_id => $self->merchant_id,
);
}
=item merchant_id
The merchant ID provided by Amazon.
=item access_key_id
Provided by Amazon.
=item secret_key
Provided by Amazon.
=item marketplace_id
L<http://docs.developer.amazonservices.com/en_US/dev_guide/DG_Endpoints.html>
=item endpoint
Ditto.
=cut
has merchant_id => (is => 'ro', required => 1);
has access_key_id => (is => 'ro', required => 1);
has secret_key => (is => 'ro', required => 1);
has marketplace_id => (is => 'ro', required => 1);
has endpoint => (is => 'ro', required => 1);
=item products
An arrayref of L<Amazon::MWS::XML::Product> objects, or anything that
(properly) responds to C<as_product_hash>, C<as_inventory_hash>,
C<as_price_hash>. See L<Amazon::MWS::XML::Product> for details.
B<This is set as read-write, so you can set the product after the
object construction, but if you change it afterward, you will get
unexpected results>.
This routine also check if the product needs upload and delete
disappeared products. If you are doing the check yourself, use
C<checked_products>.
=item checked_products
As C<products>, but no check is performed. This takes precedence.
=item sqla
Lazy attribute to hold the C<SQL::Abstract> object.
=cut
has products => (is => 'rw',
isa => ArrayRef);
has sqla => (
is => 'ro',
default => sub {
return SQL::Abstract->new;
}
);
has existing_products => (is => 'lazy');
sub _build_existing_products {
my $self = shift;
my $sth = $self->_exe_query($self->sqla->select(amazon_mws_products => [qw/sku
timestamp_string
status
listed
error_code
/],
{
status => { -not_in => [qw/deleted/] },
shop_id => $self->_unique_shop_id,
}));
my %uploaded;
while (my $row = $sth->fetchrow_hashref) {
$row->{timestamp_string} ||= 0;
$uploaded{$row->{sku}} = $row;
}
return \%uploaded;
}
has products_to_upload => (is => 'lazy');
has checked_products => (is => 'rw', isa => ArrayRef);
lib/Amazon/MWS/Uploader.pm view on Meta::CPAN
if (my $limit = $self->limit_inventory) {
my $real = $product->inventory;
if ($real > $limit) {
print "Limiting the $sku inventory from $real to $limit\n" if $self->debug;
$product->inventory($limit);
}
}
if (my $children = $product->children) {
my @good_children;
foreach my $child (@$children) {
# skip failed children, but if the current status of
# parent is failed, and we reached this point, retry.
if (! exists $self->_force_hashref->{$child} and
$existing->{$child} and
$existing->{$sku} and
$existing->{$sku}->{status} ne 'failed' and
$existing->{$child}->{status} eq 'failed') {
print "Ignoring failed variant $child\n";
}
else {
push @good_children, $child;
}
}
$product->children(\@good_children);
}
push @todo, $product;
}
if ($self->purge_missing_products) {
# nuke the products not passed
# print Dumper($existing);
my @deletions = map { $_->{sku} }
grep { !$_->{_examined} }
values %$existing;
if (@deletions) {
$self->delete_skus(@deletions);
}
}
return \@todo;
}
=item client
An L<Amazon::MWS::Client> object, built lazily, so you don't have to
pass it.
=back
=cut
has client => (is => 'lazy');
sub _build_client {
my $self = shift;
my %mws_args = map { $_ => $self->$_ } (qw/merchant_id
marketplace_id
access_key_id
secret_key
debug
logfile
endpoint/);
return Amazon::MWS::Client->new(%mws_args);
}
has _mismatch_patterns => (is => 'lazy', isa => HashRef);
sub _build__mismatch_patterns {
my $self = shift;
my $merchant_re = qr{\s+\((?:Merchant|Verkäufer):\s+'(.*?)'\s+/};
my $amazon_re = qr{\s+.*?/\s*Amazon:\s+'(.*?)'\)};
my %patterns = (
# informative only
asin => qr{ASIN(?:\s+überein)?\s+([0-9A-Za-z]+)},
shop_part_number => qr{part_number$merchant_re},
amazon_part_number => qr{part_number$amazon_re},
shop_title => qr{item_name$merchant_re},
amazon_title => qr{item_name$amazon_re},
shop_manufacturer => qr{manufacturer$merchant_re},
amazon_manufacturer => qr{manufacturer$amazon_re},
shop_brand => qr{brand$merchant_re},
amazon_brand => qr{brand$amazon_re},
shop_color => qr{color$merchant_re},
amazon_color => qr{color$amazon_re},
shop_size => qr{size$merchant_re},
amazon_size => qr{size$amazon_re},
);
return \%patterns;
}
=head1 MAIN METHODS
=head2 upload
If the products is set, begin the routine to upload them. Because of
the asynchronous way AMWS works, at some point it will bail out,
saving the state in the database. You should reinstantiate the object
and call C<resume> on it every 10 minutes or so.
The workflow is described here:
L<http://docs.developer.amazonservices.com/en_US/feeds/Feeds_Overview.html>
This has to be done for each feed: Product, Inventory, Price, Image,
Relationship (for variants).
This method first generate the feeds in the feed directory, and then
calls C<resume>, which is in charge for the actual uploading.
=head2 resume
Restore the state and resume where it was left.
This method accepts an optional list of parameters. Each parameter may be:
( run in 0.665 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )