{ "info": { "author": "David Reed", "author_email": "david@ktema.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Console", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3 :: Only" ], "description": "# Amaxa - a multi-object ETL tool for Salesforce\n\nAmaxa is a new data loader and ETL (extract-transform-load) tool for Salesforce, designed to support the extraction and loading of complex networks of records in a single operation. For example, an Amaxa operation can extract a designated set of Accounts, their associated Contacts and Opportunities, their Opportunity Contact Roles, and associated Campaigns, and then load all of that data into another Salesforce org while preserving the connections between the records.\n\nAmaxa is designed to replace complex, error-prone workflows that manipulate data exports with `VLOOKUP()` to maintain object relationships.\n\n## Installing, Building, and Testing Amaxa\n\nUsing Amaxa requires Python 3.6. To install Amaxa using `pip`, execute\n\n $ pip install amaxa\n\nMake sure to invoke within a Python 3.6+ virtual environment or specify Python 3.6 or greater as required by your operating system.\n\nAmaxa is operating system-agnostic. It has been tested primarily on Linux but is also known to work in a MINGW Windows 7 environment.\n\n### Development\n\nTo start working with Amaxa in a virtual environment, clone the Git repository. Then, create a virtual environment for Amaxa and install there:\n\n $ cd amaxa\n $ python3.6 -m venv venv\n $ source venv/bin/activate\n $ pip install -r requirements.txt -r testing-requirements.txt\n $ python setup.py install\n\nYou'll then be able to invoke `amaxa` from the command line whenever the virtual environment is active.\n\nAmaxa depends on the following packages to run:\n\n - `simple_salesforce`\n - `salesforce-bulk`\n - `pyyaml`\n - `pyjwt`\n - `cryptography`\n - `requests`\n - `cerberus`\n\n For development and testing, you'll also need\n\n - `pytest`\n - `pytest-cov`\n - `codecov`\n - `wheel`\n - `setuptools`\n\nTests are executed using `pytest`. If a valid Salesforce access token and instance URL are present in the environment variables `INSTANCE_URL` and `ACCESS_TOKEN`, integration and end-to-end tests will be run against that Salesforce org; otherwise only unit tests are run. Note that **integration tests are destructive** and require data setup before running. Run integration tests **only** in a Salesforce DX scratch org (see `.gitlab-ci.yml` for the specific testing process).\n\n## Running Amaxa\n\nThe command-line API is very simple. To extract data, given an operation definition file `op.yml` and a credential file `cred.yml`, run\n\n $ amaxa --credentials cred.yml op.yml\n\nTo perform the load of the same operation definition, just add `--load`:\n\n $ amaxa --credentials cred.yml op.yml --load\n\nOperation definitions are generally built to support both load and extract of the same object network. For details, see below. While the examples in this guide are in YAML format, Amaxa supports JSON at feature parity and with the same schemas.\n\nThe only other command-line switch provided by Amaxa is `--verbosity`. Supported levels are `quiet`, `errors`, `normal`, and `verbose`, in ascending order of verbosity.\n\nTo see usage help, execute\n\n $ amaxa --help\n\n## Supplying Credentials\n\nCredentials are supplied in a YAML or JSON file, as shown here (for username and password)\n\n version: 1\n credentials:\n username: 'test@example.com'\n password: 'blah'\n security-token: '00000'\n sandbox: True\n\nAmaxa also allows JWT authentication, for headless operation:\n\n version: 1\n credentials:\n username: 'test@example.com'\n consumer-key: 'GOES_HERE'\n jwt-key: |\n -----BEGIN RSA PRIVATE KEY-----\n \n -----END RSA PRIVATE KEY-----\n sandbox: False\n\nIf your JWT key is stored externally in a file, use the key `jwt-file` with the name of that file rather than including the key inline.\n\nLastly, if you establish authentication outside Amaxa (with Salesforce DX, for example), you can directly provide an access token and instance URL.\n\n version: 1\n credentials:\n access-token: '.....'\n instance-url: 'https://test.salesforce.com\n\n## Defining Operations\n\nOperations run with Amaxa are established by an operation definition file written in either JSON or YAML. The operation definition specifies which sObjects to extract or load in which order, and which fields on each object are desired. Amaxa handles tracing relationships between top-level objects and their children and extracts a set of CSV files to produce a complete, internally consistent data set.\n\nHere's an example of an Amaxa operation definition in YAML.\n\n version: 1\n operation:\n -\n sobject: Account\n fields:\n - Name\n - Id\n - ParentId\n -\n field: Description\n column: Desc\n transforms:\n - strip\n - lowercase\n extract:\n query: 'Industry = \"Non-Profit\"'\n -\n sobject: Contact\n file: 'Contacts-New.csv'\n field-group: readable\n outside-reference-behavior: drop-field\n extract:\n descendents: True\n -\n sobject: Opportunity\n field-group: readable\n extract:\n descendents: True\n\nThe meat of the definition is list of sObjects under the `operation` key. We list objects in order of their extraction or load. The order is important, because it plays into how Amaxa will locate, extract, and load relationships between the objects. Typically, you should start an operation definition with the highest-level object you want to extract or load. This may be `Account`, for example, or if loading a custom object with children, the parent object. Then, we list child objects and other dependencies in order below it. Objects listed later in the list may be extracted based on lookup or master-detail relationships to objects higher in the list. (More details on object-sequence patterns can be found below.)\n\nFor each object we choose to extract, we answer a few main questions.\n\n### Which records do we want to extract?\n\nRecord selection is specified with the `extract:` key. We can specify several different types of record-level extraction mechanics. The `extract` key is ignored during load operations.\n\n query: 'Industry = \"Non-Profit\"'\n\nThe `query` type of extraction pulls records that match a SOQL `WHERE` clause that you supply. (Do not include the `WHERE` keyword).\n\n descendents: True\n\nThe `descendents` type of extraction pulls records that have a lookup or master-detail relationship to any object higher in the operation definition. This relationship can be any field that is included in the selected fields for the object. For example, if extracting `Account` followed by `Contact`, with `descendents: True` specified, Amaxa will pull Contacts associated to all extracted Accounts via *any lookup field from `Contact` to `Account` that is included in the operation*. This could, for example, include `AccountId` as well as some custom field `Other_Account__c`. If another object were above `Contact` in the operation and `Contact` has a relationship to that object, Amaxa would also pull `Contact` records associated to extracted records for that object.\n\n ids:\n - 003000000000001\n - 003000000000002\n - 003000000000003\n\nThe `ids` type of extraction pulls specific records by `Id`, supplied in a list.\n\nAll types of extraction also retrieve *dependent relationships*. When an sObject higher in the operation has a relationship to an sObject lower in the operation, the Ids of referenced objects are recorded and extracted later in the process. For example, if an included field on `Account` is a relationship `Primary_Contact__c` to `Contact`, but `Account` is extracted first, Amaxa will ensure that all referenced records are extracted during the `Contact` step.\n\nThe combination of dependent and descendent relationship tracing helps ensure that Amaxa extracts and loads an internally consistent slice of your org's data based upon the operation definition you provide.\n\n### Which fields do we want to extract or load?\n\nThis is specified with the `fields` or `field-group` keys.\n\nThe easiest way to select fields is to specify `field-group: [smart|readable|writeable]`. This instructs Amaxa to automatically determine which fields to extract based on access level: `readable` is all accessible fields, `writeable` all createable and updateable fields, and `smart` will automatically select `readable` for extract operations and `writeable` for loads. The use of field groups streamlines the configuration file, but is most suitable for extract and load operations performed on the same org or related orgs, like sandboxes derived from the same production org. This is because Amaxa will extract references to, for example, Record Types and Users whose Ids may differ across unrelated orgs.\n\nIf you're moving data between unrelated orgs or wish to specify the exact field set for each sObject, use the `fields` key. The value of this key is a list whose elements are either the API name of a single field or a map specifying how to load, extract, and transform the data.\n\n fields:\n - Name\n - Industry\n\nis an example of a simple field specification.\n\n fields:\n -\n field: Description\n column: Desc\n transforms:\n - strip\n - lowercase\n\nwould extract the `Description` field, name the CSV column `Desc`, and apply the transformations `strip` (remove leading and trailing whitespace) and `lowercase` (convert text to lower case) on extracted data. On load, Amaxa would look for a CSV column `Desc`, map it to the `Description` field, and apply the same transformations to inbound data.\n\n### Where is the data going to or coming from?\n\nThe `file` key for each sObject specifies a CSV file. This is the input data for a load operation, or the output data for an extraction. Amaxa will specify `sObjectName.csv` if the key is not provided.\n\nFor loads, Amaxa will also use a `result-file` key, which specifies the location for the output Id map and error file. If not supplied, Amaxa will use `sObjectName-results.csv`. The results file has three columns: `\"Original Id\"`, `\"New Id\"`, and `\"Error\"`.\n\n### Object sequencing in an operation\n\nAs shown in the example above, to extract or load a parent object and its children, list the parent first, followed by the child, and specify `extract: descendents: True` for the child. If the parent is itself a child of a higher-level parent, you can use `descendents` there too - just make sure your operation definition starts with at least one object that is configured with `extract: all: True`, `extract: ids: `, or `extract: query: ` so that Amaxa has a designated record set with which to begin.\n\n operation:\n -\n sobject: Account\n field-group: readable\n extract:\n query: 'Industry = \"Non-Profit\"'\n -\n sobject: Contact\n field-group: readable\n extract:\n descendents: True\n\nThere are other patterns that can in specific situations be useful. It's permissible, for example, to start with the child object, and allow Amaxa to pull parent objects as dependencies. (Note that this approach may be somewhat less performant upon re-loading data, as Amaxa must run two passes to populate all of the lookup fields)\n\n operation:\n -\n sobject: Contact\n field-group: readable\n extract:\n query: 'Email != null'\n -\n sobject: Account\n field-group: readable\n extract:\n descendents: True\n\nIn this pattern, Amaxa will not find any descendents for `Account` (unless `Account` itself has a lookup to `Contact`), but it will pull the parent Accounts of all of the extracted Contacts as dependencies, because the `AccountId` field is included in the operation.\n\nJunction objects may be selected in several different ways. Suppose we have objects A and B, joined with a junction object C.\n\n 1. **We want to extract specific A records, with all of their C junctions and the B records associated to them.**\n We specify A first, then C, then B. C and B have `descendents: True` set under `extract:`.\n 2. **We want to extract all records of both A and B, along with the C records joining them**\n Specify both A and B with `extract: all: True`. Then list C afterwards, with `extract: descendents: True`.\n 3. **We want to extract all C records, with their associated A and B records.**\n Specify C first, with `extract: all: True`. Then list A and B in either order following C, and specify `extract: descendents: True`. In this situation, Amaxa won't find any descendent records for A and B (since they are parents), but it will automatically pull all records associated to the extracted C records as dependencies.\n\nWhen designing an operation, it's best to think in terms of which objects are primary for the operation, and take advantage of both descendent and dependent record tracing to build the operation sequence accordingly.\n\n## Validation\n\nAmaxa tries to warn you if you specify an operation that doesn't make sense or is invalid.\n\nBoth sObjects and `fields` entries are checked before the operation begins. All entries will be validated to ensure they exist and are accessible to the running user. Amaxa will show an error and stop if fields cannot be written or updated for a load operation (only dependent lookups must be updateable, but all fields must be createable).\n\nAmaxa will also validate, for load operations, that the specified input data matches the operation definition. For field lists specified with `fields`, the column set in the provided CSV must exactly match the field list (taking any specified `column` mappings into account). For `field-group` specifications, Amaxa allows fields that are part of the field group to be omitted from the CSV, but does not allow any extra fields in the CSV. If the `field-group: smart` choice is provided, Amaxa always validates against the `readable` field group, even on load, but will only attempt to load writeable fields.\n\nYou can control validation at the sObject level by specifying the key `input-validation` within each entry. The acceptable values are \n\n - `none`, which completely disables validation and is not recommended.\n - `default`, which applies the default semantics above.\n - `strict`, which treats `field-group` entries just like `fields`, meaning that there must be a 1:1 match between file columns and fields.\n\n## Handling Outside References and Self-Lookups\n\nAmaxa provides options for controlling its reference-tracing behavior in two circumstances that can cause issues: self-lookups and outside references.\n\n### Self Lookups\n\nA self-lookup is a relationship from an object to itself, such as `Account.ParentId`. Amaxa's default behavior is to handle self-lookups by iterating both up and down the hierarchy to ensure that all parents and children linked to a specific extracted record are also extracted. For example, given the following Account hierarchy:\n\n Amalgamated Industries\n \u2192 Technology Refining Corp.\n \u2192 Global Research\n \u2192 Applied Neogenomics\n \u2192 Dyadic Operations Inc.\n \u2192 Rossum Ltd.\n\nIf we specify the `Id` of Dyadic Operations Inc. in an extract operation, Amaxa will recurse upwards to Amalgamated Industries, and back down through the hierarchy, ultimately extracting Dyadic Operations Inc. itself, its children, its parents and grandparents, and *their* children. Then, if a descendent sObject like `Contact` is also specified, the records associated will the entire Account hierarchy will be extracted.\n\nIf this behavior isn't desired, the `self-lookup-behavior` key can be applied at the sObject level or in the map for an individual field entry. The allowed values are `trace-all`, the default, or `trace-none`, which inhibits following self-lookups.\n\nSelf-lookup behavior can be configured both at the sObject level and within the mapping for an individual field.\n\n### Outside References\n\nAn \"outside reference\", in Amaxa's terminology, is a reference from sObject B to sObject A, where\n\n - both sObjects are included in the operation;\n - sObject A is above sObject B;\n - the field value of the reference on some record of sObject B is the Id of a record of sObject A that was not extracted.\n\nIf Contact has a custom lookup to Account, `Primary_Support_Account__c`, an outside reference could occur if Amaxa extracted Accounts A, B, and C and all of their Contacts, and then found that Contact D's `Primary_Support_Account__c` lookup referred to Account Q - a record that was not included in the extraction. Because Accounts were already extracted, Amaxa can't add that record as a dependency.\n\nAmaxa offers special handling behaviors for outside references to help ensure that extracted data maintains referential integrity and can be loaded safely in another org.\n\nLike with self-lookups, outside reference behavior is specified with a key, `outside-lookup-behavior`, that can be placed at the sObject level or the field level in the definition file. The allowed options are\n\n - `include`, the default: include the outside reference in extracted data. (Errors may be thrown on load if the linked record is not present in the target environment).\n - `drop-field`: null out the outside reference when extracting and loading.\n - `error`: stop and record an error when an outside reference is found.\n\nNote that references to sObjects that aren't part of the operation at all are not considered outside references, and handler behavior is inactive for such references. For example, the `OwnerId` field is a reference to the `Queue` or `User` sObjects. If these sObjects are not included in the operation, specifying `outside-lookup-behavior: drop-field` will have no effect on the `OwnerId` field. Amaxa will log warnings when references to non-included sObjects are part of an operation.\n\nOutside reference behavior can be very useful in situations with complex dependent reference networks. A Contact with a reference to an Account other than its own, as above, is likely to constitute an outside reference. Outside reference behaviors allow for omitting such lookups from the operation, ensuring that the data extracted does not contain dangling references.\n\n## Error Behavior and Recovery\n\nBecause error recovery when loading complex object networks can be challenging and the overall load operation is not atomic, it's strongly recommended that all triggers, workflow rules, processes, validation rules, and lookup field filters be deactivated during an Amaxa load process. It's far easier to prevent errors than to fix them.\n\nAmaxa executes loads in two stages, called *inserts* and *dependents*. In the *inserts* phase, Amaxa loads records of each sObject in sequence. In the *dependents* phase, Amaxa runs updates to populate self-lookups and dependent lookups on the created records. In both phases, Amaxa stops loading data when it receives an error from Salesforce. Since Amaxa uses the Bulk API, the stoppage occurs at the end of the sObject and phase that's currently processing. \n\nIf Accounts, Contacts, and Opportunities are being loaded, and an error occurs during the insert of Contacts, Amaxa will stop at the end of the Contact insert phase. All successfully loaded Accounts and Contacts remain in Salesforce, but no work is done for the *dependents* phase. If the error occurs during the *dependents* phase, all records of all sObjects have been loaded, but dependent and self-lookups for the errored sObject and all sObjects later in the operation are not populated. \n\nDetails of the errors encountered are shown in the results file for the errored sObject, which by default is `sObjectName-results.csv` but can be overridden in the operation definition.\n\nWhen Amaxa stops due to errors, it saves a *state file*, which preserves the phase and progress of the load operation. The state file for some operation `operation.yaml` will be called `operation.state.yaml`. The state file persists the map of old to new Salesforce Ids that were successfully loaded, as well as the position the operation was in when the failure occured.\n\nShould a failure occur, you can take action to remediate the failure, including making changes to the records in your `.csv` files or altering the metadata in your org. You can then resume the load by repeating your original command and adding the `-s ` option:\n\n $ amaxa --load operation.yaml -c credentials.yaml -s operation.state.yaml\n\nAmaxa will pick up where it left off, loading only the records which failed or which weren't loaded the first time. (You may add records to the operation, in any sObject, and Amaxa will pick them up upon resume provided that the original failure was in the *inserts* phase - do not add new records if Amaxa has reached the *dependents* phase). It will also complete any un-executed passes to populate dependent and self-lookups.\n\n## API Usage\n\nAmaxa uses both the REST and Bulk APIs to do its work.\n\nWhen extracting, it consumes one Bulk API job for each sObject with `extract` set to `all` or `query`, plus approximately one API call (to the REST API) per 200 records that are extracted by Id due to dependencies or `extract` set to `descendents`.\n\nWhen loading, Amaxa uses one Bulk API batch for each 10,000 records of each sObject, plus one Bulk API batch for each 10,000 records of each sObject that has self- or dependent lookups. Only records requiring dependent processing are included in the second phase.\n\nA small number of additional API calls are used on each operation to obtain schema information for the org.\n\n## Example Data and Test Suites\n\nTwo example data suites and operation definition files are included with Amaxa in the `assets` directory. See `about.md` in each directory for information about what the data suite includes and tests and how to use it.\n\n## Limitations, Known Issues, and Future Plans\n\n - Amaxa does not support import or export of compound fields (Addresses and Geolocations), but can import and export their component fields, such as `MailingStreet`.\n - Amaxa does not support Base64 binary-blob fields.\n\nFuture plans include:\n\n - Improvements to efficiency in API use and memory consumption.\n - More sophisticated handling of references to \"metadata-ish\" sObjects, like Users and Record Types.\n - Support for importing data from external systems that does not have a Salesforce Id\n - Note that manually synthesizing Id values in input data is fine, provided they conform to the expected length and character content of Salesforce Ids.\n - Recursive logic on extraction to handle outside references.\n\n## What Does Amaxa Mean?\n\n[\u1f04\u03bc\u03b1\u03be\u03b1](http://www.perseus.tufts.edu/hopper/text?doc=Perseus%3Atext%3A1999.04.0058%3Aentry%3Da\\)%2Fmaca) is the Ancient Greek word for a wagon.\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://gitlab.com/davidmreed/amaxa", "keywords": "", "license": "GNU GPLv3", "maintainer": "", "maintainer_email": "", "name": "amaxa", "package_url": "https://pypi.org/project/amaxa/", "platform": "", "project_url": "https://pypi.org/project/amaxa/", "project_urls": { "Homepage": "https://gitlab.com/davidmreed/amaxa" }, "release_url": "https://pypi.org/project/amaxa/0.9.4/", "requires_dist": [ "pyyaml", "simple-salesforce", "salesforce-bulk", "cerberus", "requests", "pyjwt", "cryptography" ], "requires_python": ">=3.6", "summary": "Load and extract data from multiple Salesforce objects in a single operation, preserving links and network structure.", "version": "0.9.4" }, "last_serial": 5254798, "releases": { "0.9.1": [ { "comment_text": "", "digests": { "md5": "a2efce8c54861f296f9c6955cf54ac48", "sha256": "559429d2b9b0b9aeb3896a1ab0eadd8b689a7b17c51be872085f515e5e4c0d01" }, "downloads": -1, "filename": "amaxa-0.9.1-py3-none-any.whl", "has_sig": false, "md5_digest": "a2efce8c54861f296f9c6955cf54ac48", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 35175, "upload_time": "2019-01-28T23:15:25", "url": "https://files.pythonhosted.org/packages/4f/26/46d3ed45765256f80b029edb9f9760c5d2cac0b52423ddd4bf362675e117/amaxa-0.9.1-py3-none-any.whl" } ], "0.9.2": [ { "comment_text": "", "digests": { "md5": "5e7d2a6020a78a6b1471d578fd6c4dbf", "sha256": "eb17176bfce716432cd15df0094a5d11775796d73330bcd5d3eb8ff886ded36f" }, "downloads": -1, "filename": "amaxa-0.9.2-py3-none-any.whl", "has_sig": false, "md5_digest": "5e7d2a6020a78a6b1471d578fd6c4dbf", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 35190, "upload_time": "2019-01-30T13:17:35", "url": "https://files.pythonhosted.org/packages/e3/25/2acf89963e49b5d296b865ccd2cba6535af251fc1927be401d060ab32bdd/amaxa-0.9.2-py3-none-any.whl" } ], "0.9.3": [ { "comment_text": "", "digests": { "md5": "f7a61da5872ecd07e15b6a92c5dbd19e", "sha256": "33f8264c2315415cbbd33f233d79cd29fac08d88b0b3063e6cf98830ecc70e2d" }, "downloads": -1, "filename": "amaxa-0.9.3-py3-none-any.whl", "has_sig": false, "md5_digest": "f7a61da5872ecd07e15b6a92c5dbd19e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 36794, "upload_time": "2019-02-11T03:45:11", "url": "https://files.pythonhosted.org/packages/6c/ea/a4c9f061064a03f4045db07944525bfc793cd79b473b48f47ed3cff5cc14/amaxa-0.9.3-py3-none-any.whl" } ], "0.9.4": [ { "comment_text": "", "digests": { "md5": "d11d2004eca3de55201b2571024a70cc", "sha256": "e5378d3985d0345593c9f53a33f7e1ebc7070b206cc79961843435c08978cfab" }, "downloads": -1, "filename": "amaxa-0.9.4-py3.6.egg", "has_sig": false, "md5_digest": "d11d2004eca3de55201b2571024a70cc", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": ">=3.6", "size": 48060, "upload_time": "2019-05-11T02:47:52", "url": "https://files.pythonhosted.org/packages/ad/12/1b1ac6e0835587de0e6314726ae22f4c2f393fee64454a1459dc04c211a4/amaxa-0.9.4-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "46187eb9632875c39f6b42012ac5f565", "sha256": "9242723810f36481eb8344462094da6ffbdcb7bc8b13cbf219d925c1e7d54490" }, "downloads": -1, "filename": "amaxa-0.9.4-py3-none-any.whl", "has_sig": false, "md5_digest": "46187eb9632875c39f6b42012ac5f565", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 26811, "upload_time": "2019-05-11T02:47:50", "url": "https://files.pythonhosted.org/packages/53/7d/8f279a06690c4ac6b53f525244ae851a08997982fac8ad8c5678ecf20b27/amaxa-0.9.4-py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "d11d2004eca3de55201b2571024a70cc", "sha256": "e5378d3985d0345593c9f53a33f7e1ebc7070b206cc79961843435c08978cfab" }, "downloads": -1, "filename": "amaxa-0.9.4-py3.6.egg", "has_sig": false, "md5_digest": "d11d2004eca3de55201b2571024a70cc", "packagetype": "bdist_egg", "python_version": "3.6", "requires_python": ">=3.6", "size": 48060, "upload_time": "2019-05-11T02:47:52", "url": "https://files.pythonhosted.org/packages/ad/12/1b1ac6e0835587de0e6314726ae22f4c2f393fee64454a1459dc04c211a4/amaxa-0.9.4-py3.6.egg" }, { "comment_text": "", "digests": { "md5": "46187eb9632875c39f6b42012ac5f565", "sha256": "9242723810f36481eb8344462094da6ffbdcb7bc8b13cbf219d925c1e7d54490" }, "downloads": -1, "filename": "amaxa-0.9.4-py3-none-any.whl", "has_sig": false, "md5_digest": "46187eb9632875c39f6b42012ac5f565", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 26811, "upload_time": "2019-05-11T02:47:50", "url": "https://files.pythonhosted.org/packages/53/7d/8f279a06690c4ac6b53f525244ae851a08997982fac8ad8c5678ecf20b27/amaxa-0.9.4-py3-none-any.whl" } ] }