# packages

pkgId: c9151af43d8b957fd0818131c6ecca502535b8e050556c6d4f3e6dccbc70e89d
name: python-apit212
arch: src
version: 3.0.1
epoch: 0
release: 1
summary: Unofficial trading212 API
url: https://github.com/Flock92/apit212
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apit212-3.0.1-1.src.rpm
checksum_type: sha256
description:
  This API is useless without the correct ticker symbol so i've got a solution.
  ```py
  from apit212 import Tickers
  ticker = Tickers()
  print(ticker.fetch_symbols(symbol='META'))
  print(ticker.find_by_name(full_name='tesla'))
  ```

  There is quite a bit of data to read through but the key you are after is *ticker* this will return the symbol used by the trading212 platform.
  ```bash
  [{'ticker': 'FB', 'type': 'STOCK', 'currency': 'USD', 'shortName': 'META', 'fullName': 'Meta Platforms', 'description': 'Meta Platforms Inc', 'minTrade': 0.1, 'digitsPrecision': 2, 'exchangeId': 68, 'tradable': True, 'underlyingInstrumentTicker': 'FB_US_EQ', 'underlyingLeverageCoefficient': 1.0, 'dealerExclusions': [], 'maxOpenLong': 1121, 'leverage': '1:5', 'insignificantDigits': 0, 'baseTicker': nan, 'expiryDate': nan, 'minTradeSizeCoefficient': 10.0, 'isin': 'US30303M1027', 'countryOfOrigin': 'US', 'quantityPrecision': 1.0, 'priorityIndex': 25.0, 'maxTrade': nan, 'conditionalVisibility': nan, 'suspended': nan}]
  [{'ticker': 'TSLA', 'type': 'STOCK', 'currency': 'USD', 'shortName': 'TSLA', 'fullName': 'Tesla', 'description': 'Tesla, Inc.', 'minTrade': 0.1, 'digitsPrecision': 2, 'exchangeId': 68, 'tradable': True, 'underlyingInstrumentTicker': 'TSLA_US_EQ', 'underlyingLeverageCoefficient': 1.0, 'dealerExclusions': [], 'maxOpenLong': 1405, 'leverage': '1:5', 'insignificantDigits': 0, 'baseTicker': nan, 'expiryDate': nan, 'minTradeSizeCoefficient': 10.0, 'isin': 'US88160R1014', 'countryOfOrigin': 'US', 'quantityPrecision': 1.0, 'priorityIndex': 100.0, 'maxTrade': nan, 'conditionalVisibility': nan, 'suspended': nan}]
  ```

  ```py
  from apit212 import Tickers
  ticker = Tickers()
  meta = ticker.fetch_symbols(symbol='META')
  print(len(meta))
  print(meta[0]['ticker'])
  ```

  finding a ticker using the widely used ticker will often return 1 result but on some occasions you may get multiple results
  so its good practice to check the len of the string returned.
  ```bash
  1
  FB
  ```

  To trade CFD's simply call the CFD class
  ```py
  from apit212 import *
  api = Apit212()
  api.setup(username="flock92@account.api", password="pass******", mode="demo")
  cfd = CFD(cred=api)
  ```
  To trade Equity's simply call the Equity class.
  ```py
  from apit212 import *
  client = Apit212(username="flock92@account.api", password="pass******", mode="live")
  equity = Equity()
  ```


  The *auth_validate* function will return account ID and trade type.
  ```py
  validate = cfd.auth_validate()
  print(validate)
  ```

  ```bash
  {'id': '*******-****-****-****-************', 'accountId': ********, 'customerId': *********, 'tradingType': 'CFD', 'customerUuid': '********-****-****-****-************', 'frontend': 'WC4', 'readyToTrade': True, 'deviceUuid': ''}
  ```

  The *get_account* function will return your account details.
  ```py
  account = cfd.get_account()
  print(account)
  ```

  ```bash
  {'demoAccounts': [{'id': ********, 'type': 'DEMO', 'tradingType': 'CFD', 'customerId': ********,
  'createdDate': '2023-01-17T03:20:48.000+00:00', 'status': 'ACTIVE', 'registerSource': 'WC4',
  'currencyCode': 'GBP', 'readyToTrade': True}], 'liveAccounts': [{'id': ********, 'type': 'LIVE',
  'tradingType': 'CFD', 'customerId': ********, 'createdDate': '2023-01-17T03:20:32.000+00:00',
  'status': 'PENDING_ACTIVATION', 'registerSource': 'WC4', 'currencyCode': 'GBP', 'readyToTrade': False}]}
  ```

  The *get_funds* function will return the accounts funds.
  ```py
  funds = cfd.get_funds()
  print(funds)
  ```

  ```bash
  {'*******': {'accountId': ********, 'tradingType': 'CFD', 'currency': 'GBP',
  'freeForWithdraw': 486.83, 'freeForCfdTransfer': 0, 'total': 486.83,
  'lockedCash': {'totalLockedCash': 0, 'lockedCash': []}}}
  ```

  The *get_summary* returns a summary of you account.
  ```py
  summary = cfd.get_summary()
  print(summary)
  ```

  ```bash
  'open': {'unfilteredCount': 1, 'items': [{'positionId': '********-****-****-****-************', 'humanId': '********',
  'created': '2023-07-03T18:17:46.563+03:00', 'averagePrice': 192.25, 'averagePriceConverted': 150.73025341182984,
  'currentPrice': 192.2, 'value': 1054.82, 'investment': 1055.11, 'code': 'AAPL', 'margin': 212.02, 'ppl': -0.28,
  'quantity': 7, 'maxBuy': 9.0, 'maxSell': 7, 'maxOpenBuy': 2033.0, 'maxOpenSell': 2040.0, 'swap': -1.06, 'frontend': 'WC4'}]}
  ```

  The *get_companies* returns instruments avaliable to trade on the trading212 platform
  ```py
  companies = cfd.get_companies()
  print(companies)
  ```

  ```bash
  [{'ticker': 'SIGTl_EQ', 'isin': 'GB0008769993'}, {'ticker': 'PDYPY_US_EQ', 'isin': 'US3440441026'}...]
  ```

  The *get_instruments_info* will return information about an instrument.
  ```py
  info = cfd.get_instruments_info(instrument="TSLA")
  print(info)
  ```

  ```bash
  {'code': 'TSLA', 'type': 'STOCK', 'margin': 0.2, 'shortPositionSwap': -0.07030593058663,
  'longPositionSwap': -0.27928156941337, 'tsSwapCharges': '1970-01-01T23:00:00.000+02:00',
  'marginPercent': '20', 'leverage': '1:5'}
  ```

  The *get_order_info* get
  ```py
  orderInfo = cfd.get_order_info(instrument="TSLA", quamtity=1.5)
  print(orderInfo)
  ```

  ```bash
  {'buyMargin': 40.18, 'sellMargin': 40.18, 'buySwap': -0.2, 'sellSwap': -0.05}
  ```

  ```py
  tickers = ["TSLA","AAPL"]
  deviation = cfd.get_deviations(instruments=tickers)
  print(deviation)
  ```

  ```bash
  [{'request': {'ticker': 'TSLA', 'useAskPrice': False}, 'response': {'timestamp': 1694073610000, 'price': 250.68, 'period': 'd1'}}, {'request': {'ticker': 'AAPL', 'useAskPrice': False}, 'response': {'timestamp': 1694073610000, 'price': 178.18, 'period': 'd1'}}]
  ```

  The *get_position* returns infomation for a given position ID, this ID's can be exstracted from the get_summary function
  ```py
  position = cfd.get_position(position_id=274187113)
  print(position)
  ```

  ```bash
  [{'eventType': {'action': 'opened', 'source': 'MARKET_ORDER'},
  'eventNumber': {'name': 'MO3053019640', 'id': '274187113', 'frontend': 'WC4'}, 'time': '2023-08-02T22:42:54.000+03:00',
  'direction': 'sell', 'quantity': 1.0, 'price': '105.29', 'avgQuantity': 1.0, 'avgPrice': '105.2900', 'modifiedDirection':
  'sell'}]
  ```

  The *get_all_result* function will return a list of all your trading results. you will need to request each page. you may also need to pass your timezone.
  ```py
  results = cfd.get_all_results()
  print(results)
  ```

  ```bash
  {'data': [{'direction': 'buy', 'code': 'AAPL', 'quantity': 0.1, 'orderNumber': {'name': 'P************', 'link': 'positionHistory/********-****-****-****-************', 'id': '********-****-****-****-************', 'frontend': 'WC4'}, 'price': '176.6700', 'closePrice': '181.98', 'result': '0.42', 'eventNumber': {'name': 'PO3062546222', 'id': '********-****-****-****-************'}, 'eventType': 'closed', 'time': '2023-08-29T17:15:55.000+03:00', 'openingTime': '2023-08-25T11:51:15.000+03:00'}, ...], 'nextPage': 'result?perPage=20&onlyFullyClosed=false&page=2', 'currentPage': 'result?perPage=20&onlyFullyClosed=false&page=1', 'totalSize': 133}
  ```

  ```py
  orderHist = cfd.get_order_hist(page_number=1)
  print(orderHist)
  ```

  ```bash
  {'data': [], 'currentPage': 'order?filter=all&perPage=20&from=2023-09-06T02:00:00.000+03:00&to=2023-09-08T01:59:59.173+03:00&page=1', 'totalSize': 0}
  ```

  ```py
  positionHist = cfd.get_posistion_hist(page_number=1)
  print(positionHist)
  ```

  ```bash
  {'data': [], 'currentPage': 'position?perPage=20&from=2023-09-06T02:00:00.000+03:00&to=2023-09-08T01:59:59.173+03:00&page=1', 'totalSize': 0}
  ```

  The *fast_price* retruns an instruments price as a float. if the request fails None is returned
  ```py
  price = cfd.fast_price(instrument="TSLA")
  print(price)
  ```

  ```bash
  253.49
  ```

  The *chart_data* returns a dictionary with the candle date OHLC (open, high, low, close) the period requested. which is set to 1minute by default.
  ```py
  charts = cfd.chart_data(instrument="TSLA", period="ONE_MINUTE")
  print(charts)
  ```

  ```bash
  [{'request': {'ticker': 'TSLA', 'period': 'ONE_MINUTE', 'size': 500, 'useAskPrice': False}, 'response': {'candles': [[1691152740000, 259.6, 259.97, 259.43, 259.49, 47], [1691152800000, 259.38, 259.94, 259.17, 259.56, 58], [1691152860000, 259.62, 260.34, 259.62, 260.19, 42]
  ```

  The *multi_price* function will return the last qouted price for all passed instruments.
  ```py
  tickers =["TSLA","AAPL","GOOG"]
  multiprice = cfd.multi_price(instruments=tickers)
  print(multiprice)
  ```

  ```bash
  [{'ticker': 'TSLA', 'price': 251.34}, {'ticker': 'AAPL', 'price': 181.96}, {'ticker': 'GOOG', 'price': 135.18}]
  ```


  The *market_order* function submits a market order and requires the current price of the instrument.
  ```py
  targetPrice = cfd.fast_price(instrument="TSLA")
  marketOrder = cfd.market_order(instrument="TSLA", target_price=targePrice,
  quantity=1.5, take_profit=10, stop_loss=10 )
  ```

  The *limit_order* function submits a limit order
  ```py
  marketOrder = cfd.limit_order(instrument="TSLA", target_price=127,
  quantity=1.5, take_profit=10, stop_loss=10)
  ```

  The *set_limits* function allows you to modify or add a stoploss or takeprofit to an existing postion (the positionID is required to carry out this function)
  ```py
  limits = cfd.set_limits(position_id=27361748, TP=10, SL=10)
  ```

  The *add_trailing_stop* function adds a trailing stop to an existing position
  ```py
  trailing = cfd.add_trailing_stop(position_id=27361748, distance=1)
  ```

  The *close_position* function is used to close an open position. it will required the current price.
  ```py
  currentPrice = cfd.fast_price(instrument="TSLA")
  close = cfd.close_position(position_id=23948174, current_price=currentPrice)
  ```

  ```py
  cancelAll = cfd.cancel_all_orders()
  ```

  The *cancel_order* function is used to cancel a limit order.
  ```py
  cancel = cfd.cancel_order(order_id=**********)
  ```

  the *get_live* function will return the current price of a ticker
  ```py
  tickers = ["TSLA", "AAPL"]
  prices = cfd.live_price(instruments=tickers)
  print(prices)
  ```

  ```bash
  [{'request': {'ticker': 'TSLA', 'useAskPrice': False}, 'response': {'timestamp': 1690531210000, 'price': 255.8, 'period': 'd1'}}, {'request': {'ticker': 'AAPL', 'useAskPrice': False}, 'response': {'timestamp': 1690531210000, 'price': 193.29, 'period': 'd1'}}]
  ```

  The *get_funds* function will return your accounts funds.
  ```py
  funds = cfd.get_funds()
  print(funds)
  ```

  ```bash
  {'20434246': {'accountId': ********, 'tradingType': 'CFD', 'currency': 'GBP',
  'freeForWithdraw': 486.83, 'freeForCfdTransfer': 0, 'total': 486.83,
  'lockedCash': {'totalLockedCash': 0, 'lockedCash': []}}}
  ```

  the *trailing_stop* function allows you to add a trailing stop to a open position.
  ```py
  trailing_stop = cfd.trailing_stop(position_id="***-****-***", distance=0.5)
  ```

  The *add_limits* function allows you to add a stoploss and takeprofit to an existing position.
  ```py
  update_limits = client.add_limits(position_id="***-****-***", TP=1 , SL=1)
  ```
  To set a new stoploss or takeprofit just pass the distance to the TP (take profit) or SL (stop loss) params. The function will get the current price and apply the distance.

  The *all_position_hist* function will return the position history.
  ```py
  position_history = cfd.all_position_hist()
  ```

  The *all_order_hist* returns orders data
  ```py
  order_history = cfd.all_order_hist()
  ```

  The *get_instrument* function will retunr information about the instrument.
  ```py
  tsla_info = cfd.get_instruments_info(instrument='TSLA')
  print(tsla_info)
  ```

  ```bash
  {'code': 'TSLA', 'type': 'STOCK', 'margin': 0.2, 'shortPositionSwap': -0.07030593058663,
  'longPositionSwap': -0.27928156941337, 'tsSwapCharges': '1970-01-01T23:00:00.000+02:00',
  'marginPercent': '20', 'leverage': '1:5'}
  ```

  The *get_position* function will returns information for the qouted positionID.
  ```py
  position = cfd.get_position(position_id="***-****-***")
  print(position)
  ```

  ```bash
  [{'eventType': {'action': 'opened', 'source': 'MARKET_ORDER'},
  'eventNumber': {'name': 'MO3053019640', 'id': '274187113', 'frontend': 'WC4'}, 'time': '2023-08-02T22:42:54.000+03:00',
  'direction': 'sell', 'quantity': 1.0, 'price': '105.29', 'avgQuantity': 1.0, 'avgPrice': '105.2900', 'modifiedDirection':
  'sell'}]
  ```

  The *get_summary* function will return the account summary. this function can also be used to get order ID's and there current PPL
  ```py
  summary = cfd.get_summary()
  print(summary)
  ```

  ```bash
  'open': {'unfilteredCount': 1, 'items': [{'positionId': '********-****-****-****-************', 'humanId': '********',
  'created': '2023-07-03T18:17:46.563+03:00', 'averagePrice': 192.25, 'averagePriceConverted': 150.73025341182984,
  'currentPrice': 192.2, 'value': 1054.82, 'investment': 1055.11, 'code': 'AAPL', 'margin': 212.02, 'ppl': -0.28,
  'quantity': 7, 'maxBuy': 9.0, 'maxSell': 7, 'maxOpenBuy': 2033.0, 'maxOpenSell': 2040.0, 'swap': -1.06, 'frontend': 'WC4'}]}
  ```

  The *live_price* function will return the current ask price for the passed instrument.
  ```py
  ticker = ["TSLA","AAPL","GOOG"]
  live_price = cfd.live_price(instruments=ticker)
  print(live_price)
  ```

  ```bash
  [{'ticker': 'TSLA', 'price': 253.49}, {'ticker': 'AAPL', 'price': 182.08}, {'ticker': 'GOOG', 'price': 128.44}]
  ```

  The *fast_price* function will return the last qouted chart price as a float
  ```py
  price = cfd.fast_price(instrument="TSLA")
  print(price)
  ```

  ```bash
  253.49
  ```

  the *chart_data* function will return the lastest chart data for passed instrument
  ```py
  chart = cfd.chart_data(instrument="TSLA")
  print(chart)
  ```

  ```bash
  [{'request': {'ticker': 'TSLA', 'period': 'ONE_MINUTE', 'size': 500, 'useAskPrice': False}, 'response': {'candles': [[1691152740000, 259.6, 259.97, 259.43, 259.49, 47], [1691152800000, 259.38, 259.94, 259.17, 259.56, 58], [1691152860000, 259.62, 260.34, 259.62, 260.19, 42]
  ```

  The *get_deviations* function will return price deviations
  ```py
  ticker = ["TSLA","AAPL","GOOG"]
  deviations = cfd.get_deviations(instruments=ticker)
  print(deviations)
  ```

  ```bash
  [{'request': {'ticker': 'TSLA', 'useAskPrice': False}, 'response': {'timestamp': 1691136010000, 'price': 259.38, 'period': 'd1'}}, {'request': {'ticker': 'AAPL', 'useAskPrice': False}, 'response': {'timestamp': 1691136010000, 'price': 188.99, 'period': 'd1'}}, {'request': {'ticker': 'GOOG', 'useAskPrice': False}, 'response': {'timestamp': 1691136010000, 'price': 129.05, 'period': 'd1'}}]
  ```

  The *get_companies* function will return companies currently listed on T212 & their respective isin ID.
  ```py
  companies = cfd.get_companies()
  print(companies)
  ```

  ```bash
  [{'ticker': 'SIGTl_EQ', 'isin': 'GB0008769993'}, {'ticker': 'PDYPY_US_EQ', 'isin': 'US3440441026'}...]
  ```

  The *limit_order* function submit a limit order and takes quantity, target_price, take_profit & stop_loss parms.
  ```py
  limit_order = cfd.limit_order(instrument="TSLA",
  quantity=5, target_price=129, take_profit=130, stop_loss=128)
  ```

  ```
  {'account': {'dealer': 'AVUSUK', 'positions': [{'positionId': '********-****-****-****-************',
  'humanId': '**********', 'created': '2023-07-03T18:17:46.563+03:00' ...
  ```

  The *market_order* function submit a market order and takes quantity, target_price, take_profit & stop_loss parms.
  ```py
  market_order = cfd.market_order(instrument="TSLA",
  quantity=5, target_price=129, take_profit=130, stop_loss=128)
  ```

  The *cancel_order* function will cancel a pending order it requires a orderID.
  ```py
  cancel_order = cfd.cancel_order(order_id)
  ```
  You can also use the *cancel_all_orders* to cancel all pending limits orders.
  ```py
  cancel_all = cfd.cancel_all_orders()
  ```

  The *close_position* function will submit a request to cancel a open position.
  ```py
  close_position = cfd.close_position(position_id='ordexxxxx', current_price=current_price)
  ```

  ```py
  username = "flock92@account.api"
  password = "password132"
  api = Apit212()
  api.setup(username=username, password=password, mode="demo")
  cfd = CFD(cred=api)
  target_price = 128
  while True:
      sleep(60)
      price = cfd.fast_price("TSLA")
      if price <= target_price:
          marketOrder = cfd.market_order(instrument="TSLA",           target_price=price, quantity=1, take_profit=10,
          stop_loss=10)
          break
  ```

  When carrying out a trade you might run into issues and it's always good to have protocols in place to deal with these issues when they arise.
  below is a list of **some** exceptions you might come across when carrying out a trade.

  * MinQuantityExceeded
  * NoPriceException
  * InstrumentNotSupported
  * SpreadIsBiggerThanMinDistance
  * InstrumentDisabled
  * StopLossMustBeBelowCurrentPrice
  * InsufficientFundsMaxBuy
  * InsufficientFundsMaxSell
  * MarketStillNotOpen
  * QuantityPrecisionMismatch

  Just stop making the request if you get this response

  This is currently only working with equity accounts and you can only submit trades on your demo account. please read the official documentation to get a better understanding and limitations of the API.

  In order to use the official API you will need to generate a key using the trading212 app. /settings/API(Beta) then simply generate a new Key and pass it to the *Apitkey().Equity()* Class.
  ```py
  from apit212 import *
  key = "20557******************************"
  client = Apitkey()
  info = client.Equity(api_key=key, mode="live")
  ```

  Here are the functions avalible using the trading212 API-token.
  * [exchange_list](https://t212public-api-docs.redoc.ly/#operation/exchanges)
  * [instrument_list](https://t212public-api-docs.redoc.ly/#operation/instruments)
  * [pies](https://t212public-api-docs.redoc.ly/#tag/Pies)
  * [create_pie](https://t212public-api-docs.redoc.ly/#operation/create)
  * [delete_pie](https://t212public-api-docs.redoc.ly/#operation/delete)
  * [fetch_pie](https://t212public-api-docs.redoc.ly/#operation/getDetailed)
  * [update_pie](https://t212public-api-docs.redoc.ly/#operation/update)
  * [equity_orders](https://t212public-api-docs.redoc.ly/#operation/orders)
  * [limit_order](https://t212public-api-docs.redoc.ly/#operation/placeLimitOrder)
  * [market_order](https://t212public-api-docs.redoc.ly/#operation/placeMarketOrder)
  * [stop_oder](https://t212public-api-docs.redoc.ly/#operation/placeStopOrder_1)
  * [stop_limit_order](https://t212public-api-docs.redoc.ly/#operation/placeStopOrder)
  * [cancel_order](https://t212public-api-docs.redoc.ly/#operation/cancelOrder)
  * [fetch_order](https://t212public-api-docs.redoc.ly/#operation/orderById)
  * [account_data](https://t212public-api-docs.redoc.ly/#operation/accountCash)
  * [account_meta](https://t212public-api-docs.redoc.ly/#operation/account)
  * [fetch_all_posistions](https://t212public-api-docs.redoc.ly/#operation/portfolio)
  * [fetch_position](https://t212public-api-docs.redoc.ly/#operation/positionByTicker)
  * [order_history](https://t212public-api-docs.redoc.ly/#operation/orders_1)
  * [paid_out_dividends](https://t212public-api-docs.redoc.ly/#operation/dividends)
  * [export_list](https://t212public-api-docs.redoc.ly/#operation/getReports)
  * [export_csv](https://t212public-api-docs.redoc.ly/#operation/requestReport)
  * [transactions](https://t212public-api-docs.redoc.ly/#operation/transactions)

  This is an unofficial API & either myself of trading212 are responsible for the use of this API. It is strongly advised that you use a practice account before moving onto real money. *apit212 is **not** a trading bot*


pkgId: 6db89ec63e20ba033f8cb23da25adb3dd18e3c5d1b46ef2cd920cde4c75674ae
name: python-apitable
arch: src
version: 1.4.2
epoch: 0
release: 1
summary: APITable OpenAPI Python SDK
url: https://pypi.org/project/apitable/
rpm_license: UNKNOWN
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitable-1.4.2-1.src.rpm
checksum_type: sha256
description:
  [![Latest Version](https://shields.mitmproxy.org/pypi/v/apitable.svg)](https://pypi.python.org/pypi/apitable)

  It is the official package for the Fusion API of APITable, and provides an API similar to the Django ORM style.



  python3.6 +

  If you want to call apitable rest api, you need get the API Token first: visit the workbench of APITable, click the avatar in the lower left corner, and go to User Center > Developer Configuration. Click to generate Token (you need to bind your email for the first time).



  ```shell
  pip install --upgrade apitable
  ```





  ```python
  from apitable import Apitable

  apitable = Apitable("your api_token")

  dst = apitable.datasheet("your datasheet id")




  record = dst.records.create({"title": "new record from Python SDK"})
  print(record.title)


  records = dst.records.bulk_create([
      {"title": "new record from Python SDK"},
      {"title": "new record from Python SDK2"}
  ])


  record.title = "new title"
  print(record.title)



  record.update({
      "title": "new title",
      "other_field": "new value",
  })


  records = dst.records.bulk_update([
      {"recordId": "recxxxxx1", "fields":{"title": "new record.title from Python SDK"}},
      {"recordId": "recxxxxx2", "fields":{"title": "new record.title from Python SDK2"}},
  ])


  my_file = dst.upload_file( <local or network file path>)
  record.files = [my_file]


  songs = dst_songs.records.filter(artist="faye wong")
  for song in songs:
      print(song.title)


  dst_tasks.records.filter(title=None).update(status="Pending")


  book = dst_book.records.get(ISBN="9787506341271")
  print(book.title)


  record.json()


  dst.records.filter(title=None).delete()


  for field in apitable.datasheet("dstId").fields.all():
    print(field.name)


  for field in apitable.datasheet("dstId").fields.all(viewId="viewId"):
    print(field.name)


  for view in apitable.datasheet("dstId").views.all():
    print(view.name)

  ```



  Using the field name of the datasheet directly as a variable may not conform to the variable specification. So you have to fall back to using fieldId as the key, making the code less readable.

  In order to solve this problem, the Python SDK provides the function of field mapping.

  | Bug title\!                       | Bug status |
  | --------------------------------- | ---------- |
  | The page crashes after logging in | Waiting    |

  ```python
  dst = apitable.datasheet("dstt3KGCKtp11fgK0t",field_key_map={
    "title": "Bug title!",
    "state": "Bug status",
  })

  record = dst.records.get()
  print(record.title)

  print(record.state)

  record.state="Done"
  ```

  Keep the use of field id as the key

  ```python
  bug = apitable.datasheet("dstn2lEFltyGHe2j86", field_key="id")
  row = bug.records.get(flddpSLHEzDPQ="The page crashes after logging in")
  row.flddpSLHEzDPQ = "The page crashes after logging in"
  row.update({
      "flddpSLHEzDPQ": "The page crashes after logging in",
      "fldwvNDf9teD2": "Done"
  })
  ```

  When specifying `field_key="id"`, specify the corresponding key value of `field_key_map` should be `fieldId`

  ```python
  bug = apitable.datasheet("dstn2lEFltyGHe2j86", field_key="id", field_key_map={
      "title": "flddpSLHEzDPQ",
      "state": "fldwvNDf9teD2",
  })
  ```





  `dst.records` manage records in datasheets.

  | Method           | Parameter             | Type        | Description                                                                                                                                                                    | Example                                                                                                        |
  | ---------------- | --------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
  | create           | dict                  | Record      | Create a single record                                                                                                                                                         | `dst.records.create({"title":"new title"})`                                                                    |
  | bulk_create      | dict[]                | Record[]    | Create multiple records in batches                                                                                                                                             | `dst.records.bulk_create([{"title":"new record1"},{"title":"new record2"}])`                                   |
  | bulk_update      | dict[]                | Record[]    | Batch update multiple records                                                                                                                                                  | `dst.records.bulk_update([{"recordId": "recxxxxx1", "fields":{"title": "new record.title from Python SDK"}}])` |
  | all              | \*\*kwargs            | QuerySet    | Return the record collection, you can pass parameters to customize the return content                                                                                          | `dst.records.all()`                                                                                            |
  | get              | \*\*kwargs            | Record      | single record                                                                                                                                                                  | `dst.records.get(title="new title")`                                                                           |
  | get_or_create    | (defaults,\*\*kwargs) | Record,bool | Query the corresponding record through kwargs. If it does not exist, create a new record in combination with defaults. The returned bool indicates whether it is a new record. | `dst.records.get_or_create(title="new title",defaults={"status":"pending"})`                                   |
  | update_or_create | (defaults,\*\*kwargs) | Record,bool | Query the corresponding record through kwargs, and update the record with defaults. Create if it does not exist (same as get_or_create)                                        | `dst.records.update_or_create(title="new title",defaults={"status":"done"})`                                   |
  | filter           | \*\*kwargs            | QuerySet    | Filter a batch of records                                                                                                                                                      | `dst.records.filter(title="new title")`                                                                        |



  Chained calls are possible. For example, `qs = dst.records.all()` returns a batch of queried datasets.

  | Method | Parameter | Type     | Description                              | Example                                                  |
  | ------ | --------- | -------- | ---------------------------------------- | -------------------------------------------------------- |
  | filter | \*\*dict  | QuerySet | Filter out a batch of records            | `qs.filter(title="new title")`                           |
  | all    | /         | QuerySet | Returns a copy of the current record set | `qs.filter(title="new title").all()`                     |
  | get    | \*\*dict  | Record   | Single record                            | `qs.get(title="new title")`                              |
  | count  | /         | int      | Total number of records                  | `qs.filter(title="new title").count()`                   |
  | last   | /         | Record   | Last record                              | `qs.filter(title="new title").last()`                    |
  | first  | /         | Record   | First record                             | `qs.filter(title="new title").first()`                   |
  | update | \*\*dict  | Record   | Number of records successfully updated   | `qs.filter(title="new title").update(title="new title")` |
  | delete | /         | bool     | Whether the deletion is successful       | `qs.filter(title="new title").delete()`                  |



  The QuerySet queried out is a collection of Records. A single Record can obtain the value of the specified field through `record.fieldname`.

  **Please try to avoid the same name of the field name and the method property reserved by Record, the field with the same name in the table will be obscured. If it does exist, use the field mapping configuration**

  | Method/Attribute | Parameter | Type | Description                                                              | Example         |
  | ---------------- | --------- | ---- | ------------------------------------------------------------------------ | --------------- |
  | json             | /         | dict | Returns all field values of the current record                           | `record.json()` |
  | \_id             | /         | str  | \_id is a reserved attribute, returns the recordId of the current record | `record._id`    |



  The mapping relationship between Field and Python data structure. The data with empty cells in the datasheet is always null, and the records returned by the API will not contain fields with null values.

  | Field Type       | Data type           |
  | ---------------- | ------------------- |
  | SingleText       | str                 |
  | Text             | str                 |
  | SingleSelect     | str                 |
  | MultiSelect      | str[]               |
  | URL              | str                 |
  | Phone            | str                 |
  | Email            | str                 |
  | Number           | number              |
  | Currency         | number              |
  | Percent          | number              |
  | AutoNumber       | number              |
  | DateTime         | number              |
  | CreatedTime      | number              |
  | LastModifiedTime | number              |
  | Attachment       | attachment object[] |
  | Member           | unit object[]       |
  | Checkbox         | bool                |
  | Rating           | int                 |
  | CreatedBy        | unit object         |
  | LastModifiedBy   | unit object         |
  | MagicLink        | str[]               |
  | MagicLookUp      | any[]               |
  | Formula          | str / bool          |



  all method will automatically handle paging loading all resources

  _When the paging-related parameters (pageNum, pageSize) are passed in, the SDK will no longer automatically load all records, and only return the specified page data_。

  > Try to avoid using the dst.records.all method without parameters to get all the data.
  > The API can obtain a maximum of 1000 pieces of data per request. If your data volume is too large, it is close to the limit of 50000. In the case of no parameters, calling all will serially request the API 50 times. Not only is it very slow, but it consumes API request credit.

  _Return the records of the specified pagination_

  ```python
  dst.records.all(pageNum=3)
  ```

  _Use with views_

  Specifying the view id returns the same data as in the view.

  ```python
  dst.records.all(viewId="viwxxxxxx")
  ```

  _Filter data using formulas_

  ```python
  dst.records.all(filterByFormula='{title}="hello"')
  ```

  | Parameter       | Type               | Description                                                                                                 | Example                               |
  | --------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------- |
  | viewId          | str                | View ID. The request will return the filtered/sorted results in the view                                    |                                       |
  | pageNum         | int                | Default 1                                                                                                   |                                       |
  | pageSize        | int                | Default 100, maximum 1000                                                                                   |                                       |
  | sort            | dict[]             | Specify the sorting field, which overrides the view sorting conditions                                      | `[{ field: 'field1', order: 'asc' }]` |
  | recordIds       | str[]              | Returns the recordset with the specified recordId                                                           | `['recordId1', 'recordId2']`          |
  | fields          | str[]              | Only the specified fields will be returned                                                                  |                                       |
  | filterByFormula | str                | Returns matching records using formulas as filter criteria                                                  |                                       |
  | maxRecords      | int                | Limit the number of records returned, the default is 5000                                                   |                                       |
  | cellFormat      | 'json' or 'string' | Defaults to 'json', when specified as 'string' all values will be automatically converted to string format. |                                       |
  | fieldKey        | 'name' or 'id'     | Specifies the field query and returned key. The column name 'name' is used by default.                      |                                       |

  See: [Tutorial getting started with formulas](https://help.apitable.com/docs/guide/tutorial-getting-started-with-formulas)





  Can be get through the fields/views API.



  If you write to a non-existent option, the option will be created automatically



  5w records.



  10 records. In the future, we will adjust the size of this limit according to the actual situation.



  The current maximum is 1000 records. In the future, we will adjust the size of this limit according to the actual situation.



  Create a new `.env`, the content can refer to `.env.example`.

  ```shell
  cp .env .env.example
  ```

  Then modify the test code in the `test/` folder for testing.

  ```shell
  pipenv install --pre
  pipenv shell
  python -m unittest test
  ```


pkgId: d595b7e0fa02d64c013e4b67891c99446d5ef63d4235833431a842b97d6a60c0
name: python-apitally
arch: src
version: 0.12.1
epoch: 0
release: 1
url: https://apitally.io
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitally-0.12.1-1.src.rpm
checksum_type: sha256
summary:
  Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.

description:
  [![Tests](https://github.com/apitally/apitally-py/actions/workflows/tests.yaml/badge.svg?event=push)](https://github.com/apitally/apitally-py/actions)
  [![Codecov](https://codecov.io/gh/apitally/apitally-py/graph/badge.svg?token=UNLYBY4Y3V)](https://codecov.io/gh/apitally/apitally-py)
  [![PyPI](https://img.shields.io/pypi/v/apitally?logo=pypi&logoColor=white&color=%23006dad)](https://pypi.org/project/apitally/)
  This client library for Apitally currently supports the following Python web
  frameworks:
  - [FastAPI](https://docs.apitally.io/frameworks/fastapi)
  - [Starlette](https://docs.apitally.io/frameworks/starlette)
  - [Flask](https://docs.apitally.io/frameworks/flask)
  - [Django Ninja](https://docs.apitally.io/frameworks/django-ninja)
  - [Django REST Framework](https://docs.apitally.io/frameworks/django-rest-framework)
  - [Litestar](https://docs.apitally.io/frameworks/litestar)
  Learn more about Apitally on our 🌎 [website](https://apitally.io) or check out
  the 📚 [documentation](https://docs.apitally.io).

  - Middleware for different frameworks to capture metadata about API endpoints,
    requests and responses (no sensitive data is captured)
  - Non-blocking clients that aggregate and send captured data to Apitally in
    regular intervals

  Use `pip` to install and provide your framework of choice as an extra, for
  example:
  ```bash
  pip install apitally[fastapi]
  ```
  The available extras are: `fastapi`, `flask`, `django_rest_framework`,
  `django_ninja`, `starlette` and `litestar`.

  Our [setup guides](https://docs.apitally.io/quickstart) include all the details
  you need to get started.

  This is an example of how to add the Apitally middleware to a FastAPI
  application. For further instructions, see our
  [setup guide for FastAPI](https://docs.apitally.io/frameworks/fastapi).
  ```python
  from fastapi import FastAPI
  from apitally.fastapi import ApitallyMiddleware
  app = FastAPI()
  app.add_middleware(
      ApitallyMiddleware,
      client_id="your-client-id",
      env="dev",  # or "prod" etc.
  )
  ```

  This is an example of how to add the Apitally middleware to a Flask application.
  For further instructions, see our
  [setup guide for Flask](https://docs.apitally.io/frameworks/flask).
  ```python
  from flask import Flask
  from apitally.flask import ApitallyMiddleware
  app = Flask(__name__)
  app.wsgi_app = ApitallyMiddleware(
      app,
      client_id="your-client-id",
      env="dev",  # or "prod" etc.
  )
  ```

  This is an example of how to add the Apitally middleware to a Django Ninja or
  Django REST Framework application. For further instructions, see our
  [setup guide for Django](https://docs.apitally.io/frameworks/django).
  In your Django `settings.py` file:
  ```python
  MIDDLEWARE = [
      "apitally.django.ApitallyMiddleware",
      
  ]
  APITALLY_MIDDLEWARE = {
      "client_id": "your-client-id",
      "env": "dev",  # or "prod" etc.
  }
  ```

  This is an example of how to add the Apitally plugin to a Litestar application.
  For further instructions, see our
  [setup guide for Litestar](https://docs.apitally.io/frameworks/litestar).
  ```python
  from litestar import Litestar
  from apitally.litestar import ApitallyPlugin
  app = Litestar(
      route_handlers=[...],
      plugins=[
          ApitallyPlugin(
              client_id="your-client-id",
              env="dev",  # or "prod" etc.
          ),
      ]
  )
  ```

  If you need help please
  [create a new discussion](https://github.com/orgs/apitally/discussions/categories/q-a)
  on GitHub or
  [join our Slack workspace](https://join.slack.com/t/apitally-community/shared_invite/zt-2b3xxqhdu-9RMq2HyZbR79wtzNLoGHrg).

  This library is licensed under the terms of the MIT license.


pkgId: b281fa6cf692377fa7e63862fb5d30e7ae728481778aff298a38303647df5761
name: python-apitax
arch: src
version: 3.0.12
epoch: 0
release: 1
url: https://github.com/Apitax/Apitax
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitax-3.0.12-1.src.rpm
checksum_type: sha256
summary:
  Brings together Commandtax, Scriptax, Standard Library, and the API to create a powerful automation framework. Please use StarterPack to quickly incorporate additional drivers, configuration, and custom code into Apitax.

description:
  Brings together Commandtax, Scriptax, Standard Library, and the API to create a powerful automation framework. Please use StarterPack to quickly incorporate additional drivers, configuration, and custom code into Apitax.


pkgId: 349354d95f4c8ab1b5b474246f3e2af51fd1690199446e216cea908ab71371d2
name: python-apitaxdrivers
arch: src
version: 0.0.12
epoch: 0
release: 1
description: Example drivers
url: https://github.com/ShawnClake/Apitax
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitaxdrivers-0.0.12-1.src.rpm
checksum_type: sha256
summary:
  A set of example and micro drivers which can be used as the basis of more robust API drivers or combined together to form a robust application.


pkgId: 0e36f64e11d8acbdd0385481e28e3f0c4b3fc129e4449319d577bf70972ebd81
name: python-apitoolkit-django
arch: src
version: 1.4.3
epoch: 0
release: 1
summary: A Django SDK for Apitoolkit integration
url: https://pypi.org/project/apitoolkit-django/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitoolkit-django-1.4.3-1.src.rpm
checksum_type: sha256
description:
  - [Installation](#installation)
  - [Configuration](#configuration)
  - [Contributing and Help](#contributing-and-help)


pkgId: 25a15a8831a2e5e820a85479e3cc970eb1a166a35c2bd0d7c2834ddb858a3469
name: python-apitools
arch: src
version: 0.1.4
epoch: 0
release: 1
summary: Tools to play with json-schema and rest apis
url: https://github.com/hamstah/apitools
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apitools-0.1.4-1.src.rpm
checksum_type: sha256
description:
  Class to generate links defined in the links section of a json-schema.

  Generate links from [book.json](https://github.com/hamstah/apitools/blob/master/data/schemas/book.json)
  Input
  ```javascript
  	"isbn" : {
  	    "type":"string",
  	    "required":true,
  	    "pattern":"^\\d{12}(\\d|X)$"
  	}
      },
      "links" : [
  	{
  	    "rel":"self",
  	    "href":"books/{isbn}"
  	},
  	{
  	    "rel":"instances",
  	    "href": "books"
  	}
      ]
  ```
  Output
  ```python
  {
      u'instances': [u'books'],
      u'self'     : [u'books/525259838909X']
  }
  ```


pkgId: 87665ac60cfb1fdcdf922a75c41464cce5df38c9fffd079b6289a2565393509a
name: python-apkInspector
arch: src
version: 1.3.2
epoch: 0
release: 1
url: https://github.com/erev0s/apkInspector
rpm_license: Apache-2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkInspector-1.3.2-1.src.rpm
checksum_type: sha256
summary:
  apkInspector is a tool designed to provide detailed insights into the zip structure of APK files, offering the capability to extract content and decode the AndroidManifest.xml file.

description:
  ![apkInspector](https://i.imgur.com/hTzyIDG.png)
  ![PyPI - Version](https://img.shields.io/pypi/v/apkInspector)  [![CI](https://github.com/erev0s/apkInspector/actions/workflows/ci.yml/badge.svg)](https://github.com/erev0s/apkInspector/actions/workflows/ci.yml)  [![codecov](https://codecov.io/gh/erev0s/apkInspector/graph/badge.svg?token=A3YXHGXUXF)](https://codecov.io/gh/erev0s/apkInspector)

  apkInspector is a tool designed to provide detailed insights into the zip structure of APK files, offering the capability to extract content and decode the AndroidManifest.xml file. What sets APKInspector apart is its adherence to the zip specification during APK parsing, eliminating the need for reliance on external libraries. This independence, allows APKInspector to be highly adaptable, effectively emulating Android's installation process for APKs that cannot be parsed using standard libraries. The main goal is to enable users to conduct static analysis on APKs that employ evasion techniques, especially when conventional methods prove ineffective.

  Please check [this blog post](https://erev0s.com/blog/unmasking-evasive-threats-with-apkinspector/) for more details.


  [apkInspector is available through PyPI](https://pypi.org/project/apkInspector/)
  ~~~~
  pip install apkInspector
  ~~~~

  or you can clone this repository and build and install locally:
  ~~~~
  git clone https://github.com/erev0s/apkInspector.git
  cd apkInspector
  poetry build
  pip install dist/apkInspector-Version_here.tar.gz
  ~~~~


  Documentation created based on the docstrings, is available through Sphinx:
  https://erev0s.github.io/apkInspector/



  apkInspector offers a command line tool with the same name, with the following options;

  ~~~~
  $ apkInspector -h
  usage: apkInspector [-h] [-apk APK] [-f FILENAME] [-ll] [-lc] [-la] [-e] [-x] [-xa] [-m] [-sm SPECIFY_MANIFEST] [-a] [-v]

  apkInspector is a tool designed to provide detailed insights into the zip structure of APK files, offering the capability to extract
  content and decode the AndroidManifest.xml file.

  options:
    -h, --help            show this help message and exit
    -apk APK              APK to inspect
    -f FILENAME, --filename FILENAME
                          Filename to provide info for
    -ll, --list-local     List all files by name from local headers
    -lc, --list-central   List all files by name from central directory header
    -la, --list-all       List all files from both central directory and local headers
    -e, --export          Export to JSON. What you list from the other flags, will be exported
    -x, --extract         Attempt to extract the file specified by the -f flag
    -xa, --extract-all    Attempt to extract all files detected in the central directory header
    -m, --manifest        Extract and decode the AndroidManifest.xml
    -sm SPECIFY_MANIFEST, --specify-manifest SPECIFY_MANIFEST
                          Pass an encoded AndroidManifest.xml file to be decoded
    -a, --analyze         Check an APK for static analysis evasion techniques
    -v, --version         Retrieves version information
  ~~~~



  The library component of apkInspector is designed with extensibility in mind, allowing other tools to seamlessly integrate its functionality. This flexibility empowers developers to leverage the capabilities of apkInspector within their own applications and workflows. To facilitate clear comprehension and ease of use, comprehensive docstrings accompany all primary methods, providing valuable insights into their functionality, expected arguments, and return values. These detailed explanations serve as invaluable guides, ensuring that developers can quickly grasp the inner workings of apkInspector's core features and smoothly incorporate them into their projects.


   - Find end of central directory record
   - Parse central directory of APK and get details about each entry
   - Get details local header for each entry
   - Extract single or all files within an APK
   - Decode AndroidManifest.xml file
   - Identify Tampering Indicators:
     - End of Central Directory record defined multiple times
     - Unknown compression methods
     - Compressed entry with empty filename
     - Unexpected starting signature of AndroidManifest.xml
     - Tampered StringCount value
     - Strings surpassing maximum length
     - Invalid data between elements
     - Unexpected attribute size
     - Unexpected attribute names or values
     - Zero size header for namespace end nodes


  The command-line interface (CLI) serves as a practical illustration of how the methods provided by the library have been employed.


  Please take [a look at the results](https://github.com/erev0s/apkInspector/tree/main/tests/top_apps) from testing apkInspector against a set of top Play Store applications


   - Improve documentation (add examples)
   - Improve code coverage


  We welcome contributions from the open-source community to help improve and enhance apkInspector. Whether you're a developer, tester, or documentation enthusiast, your contributions are valuable.


   - [androguard](https://github.com/androguard/androguard/)
   - [medusa](https://github.com/Ch0pin/medusa)


   - Defcon 32 | [PDF](docs/presentation/apkinspector-Defon32-presentation.pdf)


  It should be kept in mind that apkInspector is an evolving project, a work in progress. As such, users should anticipate occasional bugs and anticipate updates and upgrades as the tool continues to mature and enhance its functionality. Your feedback and contributions to apkInspector are highly appreciated as we work together to improve and refine its capabilities.


pkgId: 0be23cca7e854174befda94c7b43fccddfa56e479dc34585760ea9511d2747b8
name: python-apkg
arch: src
version: 0.5.1
epoch: 0
release: 1
summary: cross-distro upstream packaging automation tool
url: https://pypi.org/project/apkg/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkg-0.5.1-1.src.rpm
checksum_type: sha256
description:
  See docs/index.md and other files in docs/ directory.

  You can also render docs into HTML using mkdocs:

      pip install -r doc-requirements.txt
      mkdocs build
      browser site/index.html



  [docs]: https://apkg.rtfd.io
  [upstream-docs]: https://pkg.labs.nic.cz/pages/apkg


pkgId: 3e97e59dd6065eda14a540cf6aa478c83ec2bb0854eb0fcf0e1fd76802731fc0
name: python-apkid
arch: src
version: 2.1.5
epoch: 0
release: 1
summary: Android Package Identifier
url: https://github.com/rednaga/APKiD
rpm_license: GPL & Commercial
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkid-2.1.5-1.src.rpm
checksum_type: sha256
description:
  |Build Status| |PyPI| |PyPI - Python Version| |PyPI - Format| |PyPI -
  License|
  APKiD gives you information about how an APK was made. It identifies
  many compilers, packers, obfuscators, and other weird stuff. It’s
  `PEiD <https://www.aldeid.com/wiki/PEiD>`__ for Android.
     Screen Shot 2019-05-07 at 10 55 00 AM
  For more information on what this tool can be used for, check out:
  -  `Android Compiler
     Fingerprinting <http://hitcon.org/2016/CMT/slide/day1-r0-e-1.pdf>`__
  -  `Detecting Pirated and Malicious Android Apps with
     APKiD <http://rednaga.io/2016/07/31/detecting_pirated_and_malicious_android_apps_with_apkid/>`__
  -  `APKiD: PEiD for Android
     Apps <https://github.com/enovella/cve-bio-enovella/blob/master/slides/bheu18-enovella-APKID.pdf>`__
  -  `APKiD: Fast Identification of AppShielding
     Products <https://github.com/enovella/cve-bio-enovella/blob/master/slides/APKiD-NowSecure-Connect19-enovella.pdf>`__


pkgId: 699f958af317d85e2255fcdb6ff6440182f132d80881439790f6aa8aba6dd02d
name: python-apkleaks
arch: src
version: 2.6.3
epoch: 0
release: 1
summary: Scanning APK file for URIs, endpoints & secrets.
url: https://github.com/dwisiswant0/apkleaks/
rpm_license: Apache License 2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkleaks-2.6.3-1.src.rpm
checksum_type: sha256
description:
  It's fairly simple to install **APKLeaks**:

  ```bash
  $ pip3 install apkleaks
  ```

  Clone repository and install requirements:
  ```bash
  $ git clone https://github.com/dwisiswant0/apkleaks
  $ cd apkleaks/
  $ pip3 install -r requirements.txt
  ```

  Pull the Docker image by running:
  ```bash
  $ docker pull dwisiswant0/apkleaks:latest
  ```

  The APKLeaks utilizes the [jadx](https://github.com/skylot/jadx) disassembler to decompile APK files. If jadx is not present in your system, it will prompt you to download it.

  Simply,
  ```bash
  $ apkleaks -f ~/path/to/file.apk

  $ python3 apkleaks.py -f ~/path/to/file.apk

  $ docker run -it --rm -v /tmp:/tmp dwisiswant0/apkleaks:latest -f /tmp/file.apk
  ```

  Here are all the options it supports.
  | **Argument**  	| **Description**                             	| **Example**                                                   |
  |---------------	|---------------------------------------------	|-------------------------------------------------------------  |
  | -f, --file    	| APK file to scanning                        	| `apkleaks -f file.apk`                                        |
  | -o, --output  	| Write to file results _(random if not set)_ 	| `apkleaks -f file.apk -o results.txt`                         |
  | -p, --pattern 	| Path to custom patterns JSON                	| `apkleaks -f file.apk -p custom-rules.json`                   |
  | -a, --args    	| Disassembler arguments                      	| `apkleaks -f file.apk --args="--deobf --log-level DEBUG"`     |
  |     --json      | Save as JSON format                         	| `apkleaks -f file.apk -o results.json --json`                 |

  In general, if you don't provide `-o` argument, then it will generate results file automatically.
  > [!TIP]
  > By default it will also save the results in text format, use `--json` argument if you want JSON output format.

  Custom patterns can be added with the following argument to provide sensitive _search rules_ in the JSON file format: `--pattern /path/to/custom-rules.json`. If no file is set, the tool will use the default patterns found in [regexes.json](https://github.com/dwisiswant0/apkleaks/blob/master/config/regexes.json) file.
  Here's an example of what a custom pattern file could look like:
  ```json
  // custom-rules.json
  {
    "Amazon AWS Access Key ID": "AKIA[0-9A-Z]{16}",
    // ...
  }
  ```
  To run the tool using these custom rules, use the following command:
  ```bash
  $ apkleaks -f /path/to/file.apk -p rules.json -o ~/Documents/apkleaks-results.txt
  ```

  We give user complete discretion to pass the disassembler arguments. For example, if you want to activate threads in `jadx` decompilation process, you can add it with `-a/--args` argument, example: `--args="--threads-count 5"`.
  ```
  $ apkleaks -f /path/to/file.apk -a "--deobf --log-level DEBUG"
  ```
  > [!WARNING]
  > Please pay attention to the default disassembler arguments we use to prevent collisions.

  `apkleaks` is distributed under Apache 2.

  Since this tool includes some contributions, and I'm not an asshole, I'll publically thank the following users for their helps and resources:
  - [@ndelphit](https://github.com/ndelphit) - for his inspiring `apkurlgrep`, that's why this tool was made.
  - [@dxa4481](https://github.com/dxa4481) and y'all who contribute to `truffleHogRegexes`.
  - [@GerbenJavado](https://github.com/GerbenJavado) & [@Bankde](https://github.com/Bankde) - for awesome pattern to discover URLs, endpoints & their parameters from `LinkFinder`.
  - [@tomnomnom](https://github.com/tomnomnom/gf) - a `gf` patterns.
  - [@pxb1988](https://github.com/pxb1988) - for awesome APK dissambler `dex2jar`.
  - [@subho007](https://github.com/ph4r05) for standalone APK parser.
  - `SHA2048#4361` _(Discord user)_ that help me porting code to Python3.
  - [@Ry0taK](https://github.com/Ry0taK) because he had reported an [OS command injection bug](https://github.com/dwisiswant0/apkleaks/security/advisories/GHSA-8434-v7xw-8m9x).
  - [@dee__see](https://twitter.com/dee__see) - for curated potentially sensitive tokens, `NotKeyHacks`.
  - [All contributors](https://github.com/dwisiswant0/apkleaks/graphs/contributors).


pkgId: 2221d698a300d7255100f9ac21dd0f7fd5e878d9d524ff111dbb229ebddad64c
name: python-apkpatcher
arch: src
version: 0.1.15
epoch: 0
release: 1
url: https://ci-yow.com
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkpatcher-0.1.15-1.src.rpm
checksum_type: sha256
summary:
  Tool use as library or in cli to patch an APK, inject some libraries inside the APK or add a custom certificate

description:
    Project: Library to patch apk (inject frida gadget)
    Author: MadSquirrel
    License: GNU General Public License v3.0
    Version: v1.0
    Date: 02-06-21



  Library to patch apk (inject frida gadget)
  this code is inspired by this project :https://github.com/badadaf/apkpatcher.

  The improvements added by this fork are the following:
  - modification of xml files such as AndroidManifest without extracting the resources. Extracting the resources usually prevents to rebuild the apk.
  - Use as an API
  - Installation as a package



    To use as library you just need to:

  ```python3
  import apkpatcher
  patcher = apkpatcher.Patcher(<apk_path>, <sdktools>, <version>)
  patcher.patching(<path_gadget>, <arch>, output_file=<output_file>, user_certificate=<true|false>)
  ```

  To use as a program you just need to:
  ```bash
  apkpatcher -a <apk_path> -g <path_gadget> -s <sdktools> -b <version> -r <arch> -o <output_file>
  ```

  For more information please visit https://apkpatcher.ci-yow.com/





  ```python3
  import apkpatcher
  patcher = apkpatcher.Patcher(<apk_path>, <sdktools>, <version>)

  patcher.add_network_certificate(<custom_certificate>)
  patcher.set_arch(<arch>)
  patcher.pause(<True|False>)

  patcher.patching(<path_gadget>, <arch>, output_file=<output_file>, user_certificate=<true|false>)
  ```



  ```python3
  sudo python3 -m pip install .
  ```


    setup your sktools as follow:
    - https://asthook.ci-yow.com/how.install.html#setup-sdktools
    install:
    - apktool
    - pip install -r requirements.txt

rpm_license:
  GNU GENERAL PUBLIC LICENSE Version 2, June 1991  Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.  Preamble  The licenses for most software are designed to take away your freedom to share and change it.  By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.  This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it.  (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.)  You can apply it to your programs, too.  When we speak of free software, we are referring to freedom, not price.  Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.  To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.  For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have.  You must make sure that they, too, receive or can get the source code.  And you must show them these terms so they know their rights.  We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.  Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software.  If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.  Finally, any free program is threatened constantly by software patents.  We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary.  To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.  The precise terms and conditions for copying, distribution and modification follow.  GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION  0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License.  The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language.  (Hereinafter, translation is included without limitation in the term "modification".)  Each licensee is addressed as "you".  Activities other than copying, distribution and modification are not covered by this License; they are outside its scope.  The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.  1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.  You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.  2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:  a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.  b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.  c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License.  (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)  These requirements apply to the modified work as a whole.  If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works.  But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.  Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.  In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.  3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:  a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,  b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,  c) Accompany it with the information you received as to the offer to distribute corresponding source code.  (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)  The source code for a work means the preferred form of the work for making modifications to it.  For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable.  However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.  If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.  4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License.  Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.  5. You are not required to accept this License, since you have not signed it.  However, nothing else grants you permission to modify or distribute the Program or its derivative works.  These actions are prohibited by law if you do not accept this License.  Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.  6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions.  You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.  7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License.  If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all.  For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.  If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.  It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices.  Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.  This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.  8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded.  In such case, this License incorporates the limitation as if written in the body of this License.  9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time.  Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.  Each version is given a distinguishing version number.  If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation.  If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.  10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission.  For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this.  Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.  NO WARRANTY  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  END OF TERMS AND CONDITIONS  How to Apply These Terms to Your New Programs  If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.  To do so, attach the following notices to the program.  It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.  {description} Copyright (C) {year}  {fullname}  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.  You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  Also add information on how to contact you by electronic and paper mail.  If the program is interactive, make it output a short notice like this when it starts in an interactive mode:  Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.  The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License.  Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.  You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary.  Here is a sample; alter the names:  Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.  {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice  This General Public License does not permit incorporating your program into proprietary programs.  If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library.  If this is what you want to do, use the GNU Lesser General Public License instead of this License.


pkgId: 5ce7213d393056e374443bedddd9d94d6c73fdda87b86e4219c8e3ec0daa20f5
name: python-apksigcopier
arch: src
version: 1.1.1
epoch: 0
release: 1
summary: copy/extract/patch android apk signatures & compare apks
url: https://github.com/obfusk/apksigcopier
rpm_license: GPLv3+
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apksigcopier-1.1.1-1.src.rpm
checksum_type: sha256
description:
  <!-- {{{1

      File        : README.md
      Maintainer  : FC Stegerman <flx@obfusk.net>
      Date        : 2023-02-08

      Copyright   : Copyright (C) 2023  FC Stegerman
      Version     : v1.1.1
      License     : GPLv3+

  }}}1 -->

  [![GitHub Release](https://img.shields.io/github/release/obfusk/apksigcopier.svg?logo=github)](https://github.com/obfusk/apksigcopier/releases)
  [![PyPI Version](https://img.shields.io/pypi/v/apksigcopier.svg)](https://pypi.python.org/pypi/apksigcopier)
  [![Python Versions](https://img.shields.io/pypi/pyversions/apksigcopier.svg)](https://pypi.python.org/pypi/apksigcopier)
  [![CI](https://github.com/obfusk/apksigcopier/workflows/CI/badge.svg)](https://github.com/obfusk/apksigcopier/actions?query=workflow%3ACI)
  [![GPLv3+](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.html)

  <a href="https://repology.org/project/apksigcopier/versions">
    <img src="https://repology.org/badge/vertical-allrepos/apksigcopier.svg?header="
      alt="Packaging status" align="right" />
  </a>

  <a href="https://repology.org/project/python:apksigcopier/versions">
    <img src="https://repology.org/badge/vertical-allrepos/python:apksigcopier.svg?header="
      alt="Packaging status" align="right" />
  </a>





  `apksigcopier` is a tool for copying [android APK
  signatures](https://source.android.com/docs/security/features/apksigning)
  from a signed APK to an unsigned one (in order to verify [reproducible
  builds](https://f-droid.org/docs/Reproducible_Builds/)).  It can also
  be used to compare two APKs with different signatures.  Its
  command-line tool offers four operations:

  * copy signatures directly from a signed to an unsigned APK
  * extract signatures from a signed APK to a directory
  * patch previously extracted signatures onto an unsigned APK
  * compare two APKs with different signatures



  ```bash
  $ mkdir meta
  $ apksigcopier extract signed.apk meta
  $ ls -1 meta
  8BEA2A77.RSA
  8BEA2A77.SF
  APKSigningBlock
  APKSigningBlockOffset
  MANIFEST.MF
  ```



  ```bash
  $ apksigcopier patch meta unsigned.apk out.apk
  ```



  ```bash
  $ apksigcopier copy signed.apk unsigned.apk out.apk
  ```



  Compare two APKs by copying the signature from the first to a copy of the second
  and checking if the resulting APK verifies.

  This command requires `apksigner`.

  ```bash
  $ apksigcopier compare foo-from-fdroid.apk foo-built-locally.apk
  $ apksigcopier compare --unsigned foo.apk foo-unsigned.apk
  ```

  NB: copying from an APK v1-signed with `signflinger` to an APK signed with
  `apksigner` works, whereas the reverse fails; see the [FAQ](#faq).



  ```bash
  $ apksigcopier --help
  $ apksigcopier copy --help      # extract --help, patch --help, etc.

  $ man apksigcopier              # requires the man page to be installed
  ```



  The following environment variables can be set to `1`, `yes`, or
  `true` to override the default behaviour:

  * set `APKSIGCOPIER_EXCLUDE_ALL_META=1` to exclude all metadata files
  * set `APKSIGCOPIER_COPY_EXTRA_BYTES=1` to copy extra bytes after data (e.g. a v2 sig)
  * set `APKSIGCOPIER_SKIP_REALIGNMENT=1` to skip realignment of ZIP entries



  ```python
  >>> from apksigcopier import do_extract, do_patch, do_copy, do_compare
  >>> do_extract(signed_apk, output_dir, v1_only=NO)
  >>> do_patch(metadata_dir, unsigned_apk, output_apk, v1_only=NO)
  >>> do_copy(signed_apk, unsigned_apk, output_apk, v1_only=NO)
  >>> do_compare(first_apk, second_apk, unsigned=False)
  ```

  You can use `False`, `None`, and `True` instead of `NO`, `AUTO`, and
  `YES` respectively.

  The following global variables (which default to `False`), can be set
  to override the default behaviour:

  * set `exclude_all_meta=True` to exclude all metadata files
  * set `copy_extra_bytes=True` to copy extra bytes after data (e.g. a v2 sig)
  * set `skip_realignment=True` to skip realignment of ZIP entries





  It currently supports v1 + v2 + v3 (which is a variant of v2).

  It should also support v4, since these are stored in a separate file
  (and require a complementary v2/v3 signature).

  When using the `extract` command, the v2/v3 signature is saved as
  `APKSigningBlock` + `APKSigningBlockOffset`.



  First it copies the APK exactly like `apksigner` would when signing it,
  including re-aligning ZIP entries and skipping existing v1 signature files.

  Then it adds the extracted v1 signature files (`.SF`, `.RSA`/`.DSA`/`.EC`,
  `MANIFEST.MF`) to the APK, using the correct ZIP metadata (either the same
  metadata as `apksigner` would, or from `differences.json`).

  And lastly it inserts the extracted APK Signing Block at the correct offset
  (adding zero padding if needed) and updates the central directory (CD) offset in
  the end of central directory (EOCD) record.

  For more information about the ZIP file format, see e.g. [the Wikipedia
  article](https://en.wikipedia.org/wiki/ZIP_%28file_format%29).



  It means that `apksigcopier` can't insert the APK Signing Block at the required
  location, since that offset is in the middle of the ZIP data (instead of right
  after the data, before the central directory).

  In other words: the APK you are trying to copy the signature to is larger than
  the one the signature was copied from.  Thus the signature cannot be copied (and
  could never have been valid for the APK you are trying to copy it to).

  In the context of verifying [reproducible builds](https://reproducible-builds.org),
  getting this error almost certainly means the build was not reproducible.



  Compared to APKs signed by `apksigner`, APKs signed with a v1 signature by
  `zipflinger`/`signflinger` (e.g. using `gradle`) have different ZIP metadata --
  `create_system`, `create_version`, `external_attr`, `extract_version`,
  `flag_bits` -- and `compresslevel` for the v1 signature files (`.SF`,
  `.RSA`/`.DSA`/`.EC`, `MANIFEST.MF`); they also usually have a 132-byte virtual
  entry at the start as well.

  Recent versions of `apksigcopier` will detect these ZIP metadata differences and
  the virtual entry (if any); `extract` will save them in a `differences.json`
  file (if they exist), which `patch` will read (if it exists); `copy` and
  `compare` simply pass the same information along internally.



  NB: because `compare` copies from the first APK to the second, it will fail when
  only the second APK is v1-signed with `zipflinger`/`signflinger`; e.g.

  ```bash
  $ compare foo-signflinger.apk foo-apksigner.apk   # copies virtual entry; works
  $ compare foo-apksigner.apk foo-signflinger.apk   # only 2nd APK has virtual entry
  DOES NOT VERIFY
  [...]
  Error: failed to verify /tmp/.../output.apk.
  ```



  A virtual entry is a ZIP entry with an empty filename, an extra field filled
  with zero bytes, and no corresponding central directory entry (so it should be
  effectively invisible to most ZIP tools).

  When `zipflinger` deletes an entry it leaves a "hole" in the archive when there
  remain non-deleted entries after it.  It later fills these "holes" with virtual
  entries.

  There is usually a 132-byte virtual entry at the start of an APK signed with a
  v1 signature by `signflinger`/`zipflinger`; almost certainly this is a default
  manifest ZIP entry created at initialisation, deleted (from the central
  directory but not from the file) during v1 signing, and eventually replaced by a
  virtual entry.

  Depending on what value of `Created-By` and `Built-By` were used for the default
  manifest, this virtual entry may be a different size; `apksigcopier` supports
  any size between 30 and 4096 bytes.

  <!--


  NB: the syntax for the environment variable changed in click >= 8.0,
  use e.g. `source_bash` instead of `bash_source` for older versions.

  For Bash, add this to `~/.bashrc`:

  ```bash
  eval "$(_APKSIGCOPIER_COMPLETE=bash_source apksigcopier)"
  ```

  For Zsh, add this to `~/.zshrc`:

  ```zsh
  eval "$(_APKSIGCOPIER_COMPLETE=zsh_source apksigcopier)"
  ```

  For Fish, add this to `~/.config/fish/completions/apksigcopier.fish`:

  ```fish
  eval (env _APKSIGCOPIER_COMPLETE=fish_source apksigcopier)
  ```
  -->





  Official packages are available in
  [Debian](https://packages.debian.org/apksigcopier) and
  [Ubuntu](https://packages.ubuntu.com/apksigcopier).

  ```bash
  $ apt install apksigcopier
  ```

  You can also manually build a Debian package using the `debian/sid`
  branch, or download a pre-built `.deb` via GitHub releases.



  Official packages are also available in
  [nixpkgs](https://search.nixos.org/packages?query=apksigcopier) and
  [Arch Linux](https://archlinux.org/packages/community/any/apksigcopier/)
  (and derivatives).



  ```bash
  $ pip install apksigcopier
  ```

  NB: depending on your system you may need to use e.g. `pip3 --user`
  instead of just `pip`.



  NB: this installs the latest development version, not the latest
  release.

  ```bash
  $ git clone https://github.com/obfusk/apksigcopier.git
  $ cd apksigcopier
  $ pip install -e .
  ```

  NB: you may need to add e.g. `~/.local/bin` to your `$PATH` in order
  to run `apksigcopier`.

  To update to the latest development version:

  ```bash
  $ cd apksigcopier
  $ git pull --rebase
  ```



  * Python >= 3.7 + click.
  * The `compare` command also requires `apksigner`.



  ```bash
  $ apt install python3-click
  $ apt install apksigner         # only needed for the compare command
  ```



  [![GPLv3+](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.html)

  <!-- vim: set tw=70 sw=2 sts=2 et fdm=marker : -->


pkgId: 430e6c941efb950564bc9da005a3fe10e4bf531f55252e99978b1d14603fd47a
name: python-apksigtool
arch: src
version: 0.1.0
epoch: 0
release: 1
summary: parse & verify android apk signing blocks
url: https://github.com/obfusk/apksigtool
rpm_license: AGPLv3+
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apksigtool-0.1.0-1.src.rpm
checksum_type: sha256
description:
  <!-- {{{1

      File        : README.md
      Maintainer  : Felix C. Stegerman <flx@obfusk.net>
      Date        : 2021-08-29

      Copyright   : Copyright (C) 2021  Felix C. Stegerman
      Version     : v0.1.0
      License     : AGPLv3+

  }}}1 -->

  <!--
  [![GitHub Release](https://img.shields.io/github/release/obfusk/apksigtool.svg?logo=github)](https://github.com/obfusk/apksigtool/releases)
  [![PyPI Version](https://img.shields.io/pypi/v/apksigtool.svg)](https://pypi.python.org/pypi/apksigtool)
  [![Python Versions](https://img.shields.io/pypi/pyversions/apksigtool.svg)](https://pypi.python.org/pypi/apksigtool)
  -->

  [![CI](https://github.com/obfusk/apksigtool/workflows/CI/badge.svg)](https://github.com/obfusk/apksigtool/actions?query=workflow%3ACI)
  [![AGPLv3+](https://img.shields.io/badge/license-AGPLv3+-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.html)

  <!--
  <a href="https://repology.org/project/apksigtool/versions">
    <img src="https://repology.org/badge/vertical-allrepos/apksigtool.svg?header="
      alt="Packaging status" align="right" />
  </a>

  <a href="https://repology.org/project/python:apksigtool/versions">
    <img src="https://repology.org/badge/vertical-allrepos/python:apksigtool.svg?header="
      alt="Packaging status" align="right" />
  </a>
  -->





  ... FIXME ...



  ... FIXME ...



  ... FIXME ...



  For Bash, add this to `~/.bashrc`:

  ```bash
  eval "$(_APKSIGCOPIER_COMPLETE=source_bash apksigtool)"
  ```

  For Zsh, add this to `~/.zshrc`:

  ```zsh
  eval "$(_APKSIGCOPIER_COMPLETE=source_zsh apksigtool)"
  ```

  For Fish, add this to `~/.config/fish/completions/apksigtool.fish`:

  ```fish
  eval (env _APKSIGCOPIER_COMPLETE=source_fish apksigtool)
  ```





  NB: this installs the latest development version, not the latest
  release.

  ```bash
  $ git clone https://github.com/obfusk/apksigtool.git
  $ cd apksigtool
  $ pip install -e .
  ```

  NB: you may need to add e.g. `~/.local/bin` to your `$PATH` in order
  to run `apksigtool`.

  To update to the latest development version:

  ```bash
  $ cd apksigtool
  $ git pull --rebase
  ```



  * Python >= 3.5 + apksigcopier + asn1crypto + click + cryptography + simplejson.



  ```bash
  $ apt install apksigcopier python3-{asn1crypto,click,cryptography,simplejson}
  ```



  [![AGPLv3+](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.html)

  <!-- vim: set tw=70 sw=2 sts=2 et fdm=marker : -->


pkgId: 13b161944313b8121b70e9ec0be00cbcbc18f2e932479fa9d64ce126c901cf89
name: python-apkutils3
arch: src
version: 2.0.14
epoch: 0
release: 1
summary: Utils for parsing apk. Type hinting supported.
url: https://github.com/Young-Lord/apkutils3
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apkutils3-2.0.14-1.src.rpm
checksum_type: sha256
description:
  A library that gets infos from APK.

  Type hinting added for many functions.

  Many getter functions are replaced by properties.



  ```shell
  pip install apkutils3
  ```



  ```shell
  $ python3 -m apkutils -h
  usage: apkutils [-h] [-m] [-s] [-f] [-c] [-V] p

  positional arguments:
    p              path

  optional arguments:
    -h, --help     show this help message and exit
    -m             Show manifest
    -s             Show strings
    -f             Show files
    -c             Show certs
    -V, --version  show program's version number and exit
  ```

  GUI tool

  ```shell
  $ python -m apkutils.gui

  ```

  Right click an `*.apk` file. Select `APK Parser`. You will see

  ![apk parser example image](imgs/apk-parser.png)



  - apkutils\axml from [kin9-0rz/axmlparser](https://github.com/kin9-0rz/axmlparser) ![Project unmaintained](https://img.shields.io/badge/project-unmaintained-red.svg)
  - apkutils\dex from [Storyyeller/enjarify](https://github.com/Storyyeller/enjarify) ![Project unmaintained](https://img.shields.io/badge/project-unmaintained-red.svg), license under Apache License 2.0.
  - Original projects: [apkutils2](https://github.com/codeskyblue/apkutils2)  ![Project unmaintained](https://img.shields.io/badge/project-unmaintained-red.svg), [apkutils](https://github.com/kin9-0rz/apkutils), license under MIT License.
  - [LibChecker](https://github.com/LibChecker/LibChecker) used for classes list generation, license under Apache License 2.0.


pkgId: 1ac786702e84482195087b0dd97671529f5539d40faf4f1e509231f99bdf1080
name: python-aplanat
arch: src
version: 0.6.20
epoch: 0
release: 1
summary: Bokeh plotting API.
url: https://github.com/epi2me-labs/aplanat
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplanat-0.6.20-1.src.rpm
checksum_type: sha256
description:
  Aplanat provides a wrappers (templates) around the bokeh library to simplify
  the plotting of common plots, with a particular focus on producing plots in
  Juypyter notebook environments.


pkgId: 57c6b4b436bb9b55327d65d53fdfff00947affdf19b7a53bcdc6d97d7224f4f9
name: python-aplos-nca-saas-sdk
arch: src
version: 0.0.2
epoch: 0
release: 1
summary: Aplos NCA SaaS SDK
url: https://aplosanalytics.com/
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplos-nca-saas-sdk-0.0.2-1.src.rpm
checksum_type: sha256
description:
  You can use 🐍 python to perform analysis with Aplos NCA using API calls via secure HTTPS requests.

  > 👉 We use this for our demos 🚀🚀 👈
  > We'll keep this maintained as we use it for our customer demos.  So keep coming back for new features.




  > 🛑 A valid Aplos NCA Account is required. 🛑
  > In order to run the executions and connect to our system, you will need a valid account with an active subscription.
  >
  > You'll need your username (email address and a password) a Cognito Client Id
  > which allows the USER_PASSWORD_AUTH flow, and your API URL.
  >
  > The Client Id (aka Cognito Client Key) and API URL can be found in your user account.
  > Your username is your email address and your password would have been provided to you during your
  > onboarding or with a "forgot password" request and reset.



  You can use the API to integrate the Aplos NCA SaaS Cloud Solution into your workflow.


  You can use the integration testing to test your environment. You can also use the Integration Testing modules
  to learn how to use the API.


  Full source code can also be found on [GitHub](https://github.com/AplosAnalytics/Aplos-NCA-SaaS-SDK)


pkgId: aa62cb73fbc1e7344d3ec6f7e06c8dc302a7c2bf4656fbf8b0998f1fcd30808c
name: python-aplotly
arch: src
version: 1.1.19
epoch: 0
release: 1
summary: please add a summary manually as the author left a blank one
url: https://pypi.org/project/aplotly/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplotly-1.1.19-1.src.rpm
checksum_type: sha256
description:
  ```bash
  pip install aplotly
  ```



  For examples please refer to the code in the `examples` folder.



  The following color palettes are available:
  `default`: The default color palette with a white background, black text and lots of different color traces
  `greys`: A palette with a white background, black text and traces from black to light grey
  `night`: A palette with a black background, white text and traces in green, white and grey
  `dark_mode`: A palette with a dark grey background, white text and traces in light green, white and grey

rpm_license:
  MIT License  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


pkgId: 2ac27556b08d1abe20ca4147b0583251de259b9e67d964985b7cfd9e9cf8bf71
name: python-aplr
arch: src
version: 10.6.3
epoch: 0
release: 1
summary: Automatic Piecewise Linear Regression
url: https://github.com/ottenbreit-data-science/aplr
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplr-10.6.3-1.src.rpm
checksum_type: sha256
description:
  Automatic Piecewise Linear Regression.


  Build predictive and interpretable parametric regression or classification machine learning models in Python based on the Automatic Piecewise Linear Regression (APLR) methodology developed by Mathias von Ottenbreit. APLR is often able to compete with tree-based methods on predictiveness, but unlike tree-based methods APLR is interpretable. Furthermore, APLR produces smoother predictions than tree-based methods. Please see the [documentation](https://github.com/ottenbreit-data-science/aplr/tree/main/documentation) for more information. Links to published article: [https://link.springer.com/article/10.1007/s00180-024-01475-4](https://link.springer.com/article/10.1007/s00180-024-01475-4) and [https://rdcu.be/dz7bF](https://rdcu.be/dz7bF). More functionality has been added to APLR since the article was published.


  ***pip install aplr***


  Available for Windows, most Linux distributions and MacOS.


  Please see example Python scripts [here](https://github.com/ottenbreit-data-science/aplr/tree/main/examples).


  Please consider sponsoring Ottenbreit Data Science by clicking on the Sponsor button. Sufficient funding will enable maintenance of APLR and further development.


  Please see the [API reference for regression](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_REGRESSION.md) and [API reference for classification](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_CLASSIFICATION.md).


pkgId: b28cda4572a68f25cad869e945c830a7e64bf196f2df6845473778901c49455b
name: python-aplus-auth
arch: src
version: 0.2.3
epoch: 0
release: 1
summary: Authorization library for A+ services
url: https://github.com/apluslms/aplus-auth
rpm_license: UNKNOWN
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplus-auth-0.2.3-1.src.rpm
checksum_type: sha256
description:
  Provides convenience functions and classes for authentication/authorization
  between [A+](https://github.com/apluslms/a-plus) and its services.



  - `aplus_auth` contains the library settings
  - `aplus_auth.requests` provides a Session class and get, post, etc. functions
  that work the same as the `requests` ones but include a JWT in the requests.
  - `aplus_auth.payload` contains the classes for payload and permission handling.
  - `aplus_auth.exceptions` contains the possible custom exceptions raised by the
  library.
  - `aplus_auth.auth` contains convenience functions for getting a JWT from the
  headers and verifying it.
  - `aplus_auth.auth.django` contains convenience functions and classes for
  handling JWT authentication/authorization in Django apps.



  Possible settings:

      <setting key>, <default>
          <explanation>

      UID,
          The unique identifier of the service.
          Always required.
      PUBLIC_KEY, None
          A string containing the public key (RSA)
          Required if JWT requests or authentication is used
      PRIVATE_KEY, None
          A string containing the private key (RSA)
          Required if JWT requests or authentication is used
      AUTH_CLASS, None
          The dotted path to a authentication class to be used by the authentication middleware
          Required only if the library's authentication middleware is used
      REMOTE_AUTHENTICATOR_UID, None
          UID of the service that check permissions and signs tokens with a trusted key (A+).
          Required for non-A+ services connecting to other services. More precisely,
          it is required if JWT requests are used, the target services do not trust UID and
          the Request class isn't overridden to use a different token.
      REMOTE_AUTHENTICATOR_URL, None
          URL of the service that check permissions and signs tokens with a trusted key (A+).
          Required if REMOTE_AUTHENTICATOR_UID is required.
      REMOTE_AUTHENTICATOR_KEY, None
          The public key of the remote authentication service.
          Required if REMOTE_AUTHENTICATOR_UID is required.
      UID_TO_KEY, {}*
          A mapping of service UIDs to their public keys. Used for determining
          whether a JWT was signed using the correct private key.
          *The following are also added if they are not specified and the UID and
          key are not None in the settings:
              <UID>: <PUBLIC_KEY>
              <REMOTE_AUTHENTICATOR_UID>: <REMOTE_AUTHENTICATOR_KEY>
      TRUSTING_REMOTES, {<netloc of REMOTE_AUTHENTICATOR_URL>: <REMOTE_AUTHENTICATOR_UID>}
          A mapping of URLs to the UIDs of the services that do not require a remotely signed JWT.
          That is, the services that have UID in their TRUSTED_UIDS. The URL can contain the scheme
          (optional), netloc of the service and a path (optional). E.g. http://example.com/example/path where
          http:// and /example/path are optional. The scheme and netloc must be lowercase. Note that
          http://example.com/example/path will match any URL that starts with that: e.g.
          http://example.com/example/pathmorepath and http://example.com/example/path/more/path will both
          match it. Use of a trailing / is recommended. If a URL matches multiple keys, the UID with
          longest key will be taken.
          This makes it possible to skip the remote token signing.
      DEFAULT_AUD_UID, None
          The default UID if the request URL isn't found in TRUSTING_REMOTES.
          Meant to be used if all services trust this service, e.g. A+ itself.
      TRUSTED_UIDS, [<REMOTE_AUTHENTICATOR_UID>]
          Issuer UIDs in JWT tokens that are trusted. This means that any JWT signed with
          the private key of a service with its UID in the list is trusted to be correct.
          The key corresponding to an UID is determined through UID_TO_KEY.
          The service's own public key is always trusted even without it being in this list.
      DISABLE_LOGIN_CHECKS, False*
          Skips login check in the login_required decorator if True. Should be False in production.
          * If used as a django app, this defaults to the DEBUG value in django settings
      DISABLE_JWT_SIGNING, False*
          Do not sign JWTs in requests if True. Should be False in production.
          * If used as a django app, this defaults to True if DEBUG (django settings) is True and
          PUBLIC_KEY is None, otherwise to False



  Add `aplus_auth` to `INSTALLED_APPS` and add the settings to the django settings file inside
  an APLUS_AUTH dict. E.g.

      
      APLUS_AUTH = {
          "PRIVATE_KEY": <private key>,
          "PUBLIC_KEY": <public key>,
          ...
      }



  Call `init_settings` (from the main package: `aplus_auth.init_settings`) with the settings as
  keyword arguments.



  The RSA keys need to be either in PEM or SSH format. Example commands on how to generate them:

  ```

  openssl genrsa -out private.pem 2048

  openssl rsa -in private.pem -out public.pem -pubout
  ```



  You can use the management command gentoken:

  `python3 manage.py gentoken -h`

  This requires `aplus_auth` to be in `INSTALLED_APPS` and the private key to be set.





  1. Create an authentication class by subclassing `aplus_auth.auth.django.ServiceAuthentication`.
  See the class description for implementation details.
  2. Add `aplus_auth.auth.django.AuthenticationMiddleware` to middlewares and set
      `AUTH_CLASS` to the dotted path of the new class.
  3. If authentication succeeds, request.user is the user returned by the class and request.auth is
  the payload of the JWT. Otherwise, they are `AnonymousUser` and `None`, respectively.

  Additionally, `aplus_auth.auth.django.login_required` can be used to decorate a view function to
  return 501 or redirect if the user hasn't logged in (!!! `request.user.is_authenticated` is used to
  check whether they are logged in). The login check can also be disabled with the
  `DISABLE_LOGIN_CHECKS` setting for debugging purposes.



  1. Create an authentication class by subclassing `aplus_auth.auth.django.ServiceAuthentication` and
  DRF's `BaseAuthentication`. See the class description for implementation details.
  2. Add the new class to the DRF authentication classes
  3. If authentication succeeds, request.user is the user returned by the class and request.auth is
  the payload of the JWT.


pkgId: 77f1b195f8a78d7f93e2faf8bde578c19804f693243f21b2dd728d7901ec930e
name: python-aplustools
arch: src
version: 1.4.8.5
epoch: 0
release: 1
summary: A collection of helpful tools
url: https://pypi.org/project/aplustools/
rpm_license: GPL-3.0-or-later
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aplustools-1.4.8.5-1.src.rpm
checksum_type: sha256
description:
  [![Active Development](https://img.shields.io/badge/Maintenance%20Level-Actively%20Developed-brightgreen.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d)
  [![Build Status](https://github.com/Adalfarus/aplustools/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Adalfarus/aplustools/actions)
  [![License: GPL-3.0](https://img.shields.io/github/license/Adalfarus/aplustools)](https://github.com/Adalfarus/aplustools/blob/main/LICENSE)



  aplustools is a simple, user-friendly Python library for performing amazing tasks. It simplifies complex processes, allowing you to achieve more with less code. Developed with a focus on ease of use and efficiency, aplustools is the go-to solution for Python developers looking to enhance their projects.



  - Easy to use
  - Highly efficient
  - Supports multiple platforms
  - Regular updates and support
  - Comprehensive documentation



  You can install aplustools via pip:

  ```sh
  pip install aplustools --upgrade
  ```

  Or clone the repository and install manually:

  ```sh
  git clone https://github.com/Adalfarus/aplustools.git
  cd aplustools
  python -m build
  ```



  Here are a few quick examples of how to use aplustools:


  ```python
  from aplustools.web.search import Search, GoogleSearchCore


  searcher = Search(GoogleSearchCore(advanced=True))
  results = searcher.search("Cute puppies", num_results=10)


  print(results)

  from aplustools.web.utils import WebPage

  web_page = WebPage(results[0].get("url"))

  response = None
  if web_page.crawlable:  # Google search does this automatically at the moment
      response = web_page.page.content
  print(response)

  ```


  ```python
  from aplustools.web.request import UnifiedRequestHandler, UnifiedRequestHandlerAdvanced
  import os


  handler = UnifiedRequestHandler()


  handler.fetch('http://example.com', async_mode=False)

  handler.fetch('http://example.com', async_mode=True)


  adv_handler = UnifiedRequestHandlerAdvanced()  # It can also handle image content


  adv_handler.request('GET', 'http://example.com', async_mode=False)

  adv_handler.request('GET', 'http://example.com', async_mode=True)

  folder_path = "./test_data/images"
  os.makedirs(folder_path, exist_ok=True)


  image_content = adv_handler.request('GET', 'http://example.com/image.png', async_mode=False, return_type='binary')
  with open(os.path.join(folder_path, './image.png'), 'wb') as file:
      file.write(image_content)


  image_content_async = adv_handler.request('GET', 'http://example.com/image.png', async_mode=True, return_type='binary')
  with open(os.path.join(folder_path, './image_async.png'), 'wb') as file:
      file.write(image_content_async)

  ```


  ````python
  from aplustools.data.imagetools import ImageManager, OnlineImage
  import os

  os.makedirs("./test", exist_ok=True)
  manager = ImageManager("./test", use_async=True)
  image_index = manager.add_image(OnlineImage, "somewhere.com/image.jpg")

  manager.execute_func(image_index, "download_image")
  manager.execute_func(image_index, "convert_to_grayscale")
  manager.execute_func(image_index, "apply_filter")  # Default is blur
  manager.execute_func(image_index, "rotate_image", 12)
  manager.execute_func(image_index, "save_image_to_disk")  # Overwrites downloaded file

  ````


  ```python
  from aplustools.data.imagetools import OnlineImage, OfflineImage, ImageFilter, SVGCompatibleImage
  import os


  folder_path = "./images"
  os.makedirs(folder_path, exist_ok=True)
  test_url = "someImage.url"
  base64_image_str = ("data:image/jpeg;base64,...")


  online_image = OnlineImage(current_url=test_url, base_location=folder_path, one_time=True)
  download_result = online_image.download_image(folder_path, online_image.current_url, "downloaded_image", "jpg")
  if not download_result[0]:
      raise Exception("Failed to download image.")
  else:
      online_image.convert_to_grayscale()
      online_image.save_image_to_disk(os.path.join(folder_path, "image.png"))


  offline_image = OfflineImage(data=base64_image_str, base_location=folder_path)
  save_result = offline_image.base64(folder_path, "base64_image", "png")
  if not save_result:
      raise Exception("Failed to save base64 image.")


  offline_image.resize_image((100, 100))
  offline_image.rotate_image(45)
  offline_image.convert_to_grayscale()
  offline_image.apply_filter(ImageFilter.BLUR)
  offline_image.save_image_to_disk(os.path.join(folder_path, "transformed_image.png"))


  image_processor = SVGCompatibleImage("someSvg.svg", 300,
                                       (667, 800), magick_path=r".\ImageMagick",
                                       base_location='./')
  image_processor.save_image_to_disk()


  from aplustools.io.environment import absolute_path, remv, copy

  a_img_path = absolute_path(os.path.join(folder_path, "downloaded_image.jpg"))

  copy(a_img_path, str(folder_path) + "downloaded_image" + " - Copy.jpg")

  remv(a_img_path)  # Remove the original image
  remv(folder_path)  # Remove the base directory

  ```


  ```python
  from aplustools.data.faker import TestDataGenerator

  test_data = TestDataGenerator()
  print(test_data.generate_random_identity())

  print("\n", end="")
  print(test_data.generate_random_name())
  print(test_data.generate_random_email())
  print(test_data.generate_random_password())
  print(test_data.generate_random_phone_number())
  print(test_data.generate_random_address())
  print(test_data.generate_random_birth_date())

  ```


  ```python
  from aplustools.utils.dummy import Dummy3  # Dummy3 is for Python 3
  import math

  dummy = Dummy3()


  dummy.attr.func("", int3="")
  dummy["Hello"] = 1
  del dummy[1]
  reversed(dummy)
  if not "Dummy" in dummy:
      dummy.keys = ["1"]
  print(dummy.keys + dummy)
  var1 = +dummy
  var2 = -dummy
  var3 = ~dummy
  print(var1, var2, var3)

  hash(dummy)
  abs(dummy)
  round(dummy)
  complex(dummy)
  oct(dummy)
  repr(dummy)
  bytes(dummy)
  format(dummy, "DD")

  math.trunc(dummy)
  dummy << dummy
  dummy >> dummy
  dummy -= 1_000_000
  num = 1
  num *= dummy

  if dummy:
      print(True)
  else:
      print(False)

  for x in dummy:
      print(x)

  type(dummy)
  print(dummy, "->", int(dummy), list(dummy), tuple(dummy), float(dummy))

  ```


  ```python
  from aplustools.utils.hasher import hashed_latest, hashed_wrapper_latest, reducer, big_reducer, num_hasher

  inp = "Hello beautiful world, how are you today, lovely star?"
  inp2 = "Hello World, kitty"

  desired_length = 64

  hashed_inp = hashed_wrapper_latest(inp, desired_length, hash_func=hashed_latest)
  hashed_inp2 = hashed_wrapper_latest(inp2, desired_length, hash_func=hashed_latest)

  print(f"{inp} ({len(inp)}) -> {hashed_inp} ({len(hashed_inp)})\n{inp2} ({len(inp2)}) -> {hashed_inp2} ({len(hashed_inp2)})")

  num_hashed_inp = num_hasher(inp, desired_length)
  num_hashed_inp2 = num_hasher(inp2, desired_length)

  print(f"{inp} ({len(inp)}) -> {num_hashed_inp} ({len(num_hashed_inp)})\n{inp2} ({len(inp2)}) -> {num_hashed_inp2} ({len(num_hashed_inp2)})")

  acceptable_chars = range(100, 200)

  num_hashed_inp_uni = num_hasher(inp, desired_length, acceptable_chars)
  num_hashed_inp_uni_2 = num_hasher(inp2, desired_length, acceptable_chars)

  print(f"{inp} ({len(inp)}) -> {num_hashed_inp_uni} ({len(num_hashed_inp_uni)})\n{inp2} ({len(inp2)}) -> {num_hashed_inp_uni_2} ({len(num_hashed_inp_uni_2)})")

  ```


  ```python
  from aplustools.utils.genpass import SecurePasswordManager, GeneratePasswords

  manager = SecurePasswordManager()
  password = manager.generate_ratio_based_password_v2(length=26, letters_ratio=0.5, numbers_ratio=0.3,
                                                      punctuations_ratio=0.2, exclude_similar=True)
  manager.add_password("example.com", "json-the-greatest", password)
  manager.store_in_buffer("example.com", 0)  # Stores unencrypted password in a buffer
  print(manager.get_password("example.com"), "|", manager.use_from_buffer(0))  # for faster access.

  print(GeneratePasswords.generate_custom_sentence_based_password_v1(
      "Exploring the unknown -- discovering new horizons...", random_case=True, extra_char="_",
      char_position="keep" or 0, num_length=5, special_chars_length=2))




  from aplustools.utils.genpass import ControlCodeProtocol, SecureSocketServer, SecureSocketClient


  prot = ControlCodeProtocol()


  client = SecureSocketClient(prot)
  sock = client.get_socket()
  server = SecureSocketServer(sock, prot)


  client.start_and_exchange_keys()  # Client has to start first
  server.start_and_exchange_keys()


  client.add_control_code("shutdown")
  client.add_message("HELLO SERVER")
  client.sendall()  # The message is still received as it sends one chunk here




  server.shutdown_client()
  print("DONE1")



  from aplustools.utils.genpass import ControlCodeProtocol, ServerMessageHandler, ClientMessageHandler
  import threading

  prot = ControlCodeProtocol()


  encoder = ClientMessageHandler(prot)


  connection = encoder.get_socket()

  decoder = ServerMessageHandler(connection, prot)


  encoder.add_message("Hello Server")
  encoder.send_control_message("shutdown")


  encoder.start()
  threading.Thread(decoder.listen_for_messages()).start()  # Blocking
  encoder.flush()  # Blocking until connection is established

  ```


  ```python
  from aplustools.data.updaters import GithubUpdater, VersionNumber
  from aplustools.io.environment import get_temp
  from aplustools import set_dir_to_ex
  import sys
  import os

  set_dir_to_ex()

  __version__ = VersionNumber("0.0.1")


  updater = GithubUpdater("Adalfarus", "unicode-writer", "py")  # If you want to use exe
  latest_version = updater.get_latest_tag_version()             # you need to compile them
                                                                

  if __version__ >= latest_version:
      sys.exit()


  path, zip_path = os.path.join(os.getcwd(), "update"), os.path.join(get_temp(), f"apt-update_{latest_version}")

  os.makedirs(path, exist_ok=True)
  os.makedirs(zip_path, exist_ok=True)


  update_thread = updater.update(path, zip_path, latest_version, implementation="none",
                                 host="localhost", port=1264, non_blocking=True, wait_for_connection=True)
  update_thread.start()


  progress_bar = 1
  for i in updater.receive_update_status():
      print(f"{i}%", end=f" PB{progress_bar}\n")
      if i == 100:
          progress_bar += 1  # Switch to second progress bar, when the downloading is finished
  update_thread.join()

  ```


  ```python
  from aplustools.package.argumint import ArgStruct, ArguMint
  from typing import Literal
  import sys


  def sorry(*args, **kwargs):
      print("Not implemented yet, sorry!")


  def help_text():
      print("Build -> dir/file or help.")


  def build_file(path: Literal["./main.py", "./file.py"] = "./main.py", num: int = 0):
      """
      build_file
      :param path: The path to the file that should be built.
      :param num:
      :return None:
      """
      print(f"Building file {path} ..., {num}")


  from aplustools.package import timid

  timer = timid.TimidTimer()

  arg_struct = {'apt': {'build': {'file': {}, 'dir': {'main': {}, 'all': {}}}, 'help': {}}}


  builder = ArgStruct()
  builder.add_command("apt")
  builder.add_nested_command("apt", "build", "file")

  builder.add_nested_command("apt.build", "dir", {'main': {}, 'all': {}})




  builder.add_command("apt.help")


  print(builder.get_structure())  # Best to cache this for better times (by ~15 microseconds)

  parser = ArguMint(default_endpoint=sorry, arg_struct=arg_struct)
  parser.add_endpoint("apt.help", help_text)

  parser.add_endpoint("apt.build.file", build_file)

  sys.argv[0] = "apt"




  parser.parse_cli(sys, "native_light")
  print(timer.end())

  ```


  ```python
  from aplustools.data.compressor import FileContainerV3, BrotliChunkCompressor
  import os


  compressor = BrotliChunkCompressor()
  container = FileContainerV3(compressor, block_size=2048*2048)

  image_data = {}
  for file in os.listdir("./images"):
      if file.lower().endswith(".png"):
          with open(os.path.join("./images", file), "rb") as f:
              image_file_data = b''.join(f.readlines())
              image_data[file] = image_file_data

  for file_name, image in image_data.items():
      container.add_file(file_name, image)


  compressed_data = container.get_compressed_container()

  print("Compression done")

  with open("./files.bin", "wb") as f:
      f.write(compressed_data)

  print("Wrote bin")


  try:
      decompressed_images = []
      for i in range(len(image_data)):
          decompressed_image = container.extract_file(compressed_data, i)
          decompressed_images.append(decompressed_image)
  except Exception as e:
      print("Indexing not possible, error", e, "\n")
      decompressed_images = []
      for file_name in image_data.keys():
          decompressed_image = container.extract_file(compressed_data, file_name)
          decompressed_images.append(decompressed_image)
  compression_ratio = len(compressed_data) / sum(len(x) for x in image_data.values())

  print(f"Original size: {sum(len(x) for x in image_data.values())} bytes")
  print(f"Compressed size: {len(compressed_data)} bytes")
  print(f"Compression ratio: {compression_ratio:.2f}")

  for i, decompressed_image in enumerate(decompressed_images):
      with open(f"./decompressed_images/image{i}.png", "wb") as f:
          f.write(decompressed_image)

  ```


  ```python
  from aplustools.io import environment as env
  from aplustools.io import loggers


  env.set_working_dir_to_main_script_location()


  p_logger = loggers.PrintLogger("my_logs_file.log", show_time=True, capture_print=False,
                       overwrite_print=True, print_passthrough=False, print_log_to_stdout=True)



  logger = loggers.monitor_stdout(logger=p_logger) # Return sys.stdout, so you can make sure it worked


  p_logger.log("Hello, world!")


  print("Hello, beautiful world!")


  p_logger.close()

  ```
  (If you want to ensure that all dependencies are installed please run `upype import aplustools; aplustools.install_all_dependencies()`)
  (Correct shortform for aplustools is apt, so please use ```import aplustools as apt``` for consistency)

  There are multiple clis added through this package:


  ```bash
  C:\Users\user_>pype
  Enter Python expression: 1 + 2
  3

  C:\Users\user_>pype 1 // 3
  0
  ```

  ```bash
  C:\Users\user_>upype import aplustools; print(aplustools.__version__)
  1.4.4

  C:\Users\user_>upype
  Enter Python code (type 'end' on a new line to finish):
  ... class Test:
  ...     def hello_word(self):
  ...             print("Hello, you!")
  ... test = Test()
  ... test.hello_word()
  ... end
  Hello, you!
  ```
  These can also be used in e.g. batch files like this
  ```batch
  @echo off
  set /p id=Enter ID:
  upype from aplustools.utils.genpass import GeneratePasswords; print(GeneratePasswords.generate_custom_sentence_based_password_v1("""%id%""", random_case=True, extra_char="""_""", char_position=0, num_length=5, special_chars_length=2))
  pause

  ```


  Can currently run tests with ```apt tests run``` and show a basic help using ```apt help```.


  For more detailed usage and examples, check out our [documentation](https://github.com/adalfarus/aplustools/wiki).


  [PEP 8 -- Style Guide for Python Code](https://peps.python.org/pep-0008/#naming-conventions)

  For modules I use 'lowercase', classes are 'CapitalizedWords' and functions and methods are 'lower_case_with_underscores'.

  Dependencies (except for the standard libraries) are:
  - [`none`]
    - data.database
    - io.environment
    - io.loggers
    - data.faker
    - utils.dummy
    - utils.hasher
    - package.lazy_loader
    - package.timid
  - [`requests`]
    - data.github-updater-none
    - data.updaters
    - data.github-updater-cmd
  - data.github-updater-gui - [`requests`, `PySide6`]
  - data.imagetools - [`Pillow`, `aiohttp`, `requests`, `wand`]
  - data.advanced_imagetools - [`opencv-python`, `aiohttp`, `wand`, `pillow_heif`]
  - web.search, web.utils - [`requests`, `BeautifulSoup4`]
  - utils.genpass - [`cryptography`]
  - web.request - [`requests`, `aiohttp`]
  - utils.compressor - [`brotli`, `zstandard`, `py7zr`]
  - io.gui - [`PySide6`]

  Sub-Modules that may be removed in future updates due to being hard to support or simply unneeded.

  - database (maybe unneeded and hard to support if more dbs are added -> new_database is being developed)
  - loggers (maybe unneeded)



  We welcome contributions! Please see our [contributing guidelines](https://github.com/adalfarus/aplustools/blob/main/CONTRIBUTING.md) for more details on how you can contribute to aplustools.

  1. Fork the repository
  2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
  3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
  4. Push to the branch (`git push origin feature/AmazingFeature`)
  5. Open a pull request



  aplustools is licensed under the GPL-3.0 License - see the [LICENSE](https://github.com/adalfarus/aplustools/blob/main/LICENSE) file for details.


pkgId: 20b2dd78b9b2f50b36724118b1c4ff9eaae94d8a03e5c8ddd041843b70ca8f99
name: python-apminsight
arch: src
version: 1.5.2
epoch: 0
release: 1
summary: Site24x7 application performance monitoring
url: https://site24x7.com
rpm_license: LICENSE.txt
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apminsight-1.5.2-1.src.rpm
checksum_type: sha256
description:
  Monitor and optimize the performance of your Python application with the Site24x7 APM Insight Python agent. The agent provides you with information on the response time, throughput, database operations, and errors of your application. Keep track of these metrics over time to identify where they can be improved for better performance.
  Make sure you have a Site24x7 account before you use an APM Insight agent to monitor metrics.
  Requirements: Python version 3.7.0 and above.
  Supported frameworks: Bottle, CherryPy, Django, Flask, and Pyramid.
  Supported databases and components: PyMySQL, Psycopg2, Pymemcache, Redis, SQLite, Cassandra, Jinja2, Genshi, and Mako.
  Supported HTTP libraries: http.client, httplib2, httpx, urllib, urllib3.
  **S247DataExporter**
      The S247DataExporter is an independent process dedicated for communicating application metrics and traces to the Site24x7 server.
  **Installation instructions for S247DataExporter:**
  * Run the following commands in your terminal
          wget -O "InstallDataExporter.sh" https://staticdownloads.site24x7.com/apminsight/S247DataExporter/linux/InstallDataExporter.sh
          sudo sh InstallDataExporter.sh install -lk <S247_LICENSE_KEY>
  * If you are using proxy, add the following snippet to the command 'sudo sh InstallDataExporter.sh install -lk <S247_LICENSE_KEY>'
          -pau <Proxy auth username> -pap <Proxy auth password> -psh <Proxy server host:Proxy server port>  -psph <Boolean value whether to use HTTPS protocol for proxy> -bp <Bollean value whether to use proxy or not>
    > *Note: By default, S247DataExporter uses 20021 as the AGENT STATUS PORT and 20022 as the AGENT DATA PORT.
  If you want to run the S247DataExporter in different ports, add the below configurations to the above mentioned command.
  | Option | Description  |
  | ------ | ------ |
  | '-asp <Agent Status Port>' | To configure the custom Exporter Status Port |
  | '-adp <Agent Data Port>' | To configure the custom Exporter Data Port |
  | '-sld <Directory path>' | To log the Exporter logs to a custom directory |
  | '-sll <Log level>' | To configure the Log level of Exporter logs |
  | '-sls <Exporter log size>' | To configure the Exporter Log file size |
  **Installation instructions for the S247 Python agent:**
  If you are using a virtual environment, activate the virtual environment and run the following command to install the S247 APMINSIGHT Python package into your virtual environment
          pip install apminsight
  **Integrate the Python agent to your application**
  * *Using the command line* \
     You can use the below command for adding extra configurations:
          $ apminsight-run -lk "license_key" <apm-options> <user application execute command>
  | apm-options | Description |
  | ------ | ------ |
  | -lk, --license_key | To add the site24x7 license key or device key |
  | -apmname, --apm_app_name | To add the APM application name |
  | -ald, --apm_log_dir | To add a custom log directory for storing APM logs |
  | -ad, --apm_debug | To enable debug mode |
  | -aeh, --apm_exp_host | To configure the S247DataExporter host |
  | -aesp, --apm_exp_status_port | To configure the S247DataExporter status port |
  | -aedp, --apm_exp_data_port | To configure the S247DataExporter data port |
  Examples:
   If you want to configure the application name, use the below command:
        apminsight-run --apm_app_name "monitorname" <user application execute command>
    If you want to configure the directory location, use the below command:
        apminsight-run --apm_app_name "monitorname" --apm_log_dir "logs location" <user application execute command>
  * *Using the code snippet*
    > Note: Update the configuration in the code snippet below and place it at the top of your application route file or application start file.
          from apminsight import initialize_agent
          initialize_agent({
              "appname" : "<Your application name>",
              "license_key" : "< your site24x7 license key>",
              '''change if  S247DataExporter is not running in the default ports(20021, 20022):'''
              "exporter_status_port" : "<S247DataExporter status port>",
              "exporter_data_port" : "<S247DataExporter data port>",
              '''If you are running S247DataExporter on a separate machine/server or as a Docker container:'''
              "exporter_host" : "<HostName/ContainerName where S247DataExporter is running>"
          })
    For example, for Django applications, add the code snippet at the top of the Django settings.py file. The name of your Django application will be automatically detected by your WSGI_APPLICATION in the application's settings.py.
    For other applications and frameworks, such as Flask, add the code snippet at the top of the application start file.
  * *Configure the agent using environment variables*
  | Configuration | Description |
  | ------ | ------ |
  | $ export S247_LICENSE_KEY=< license key > | To add your site2x7 License or Device key |
  | $ export APM_APP_NAME=<Your application name > | To add the APM application name |
  | $ export APM_LOGS_DIR=< logs storing path > | To configure the log directory |
  | $ export APM_EXPORTER_STATUS_PORT=< S247DataExporter status port >,
  $ export APM_EXPORTER_DATA_PORT=< S247DataExporter data port > | To change the S247DataExporter's default running ports (20021, 20022) |
  | $ export APM_EXPORTER_HOST=< HostName/ContainerName where S247DataExporter is running > | To change the host of the S247DataExporter to a different machine or server or as a Docker container |
  * Restart your WSGI application and Perform some transactions so that the agent can collect data.
  * Log in to your Site24x7 account.
  * Navigate to APM Insight and click your application to view application metrics.
  * You can view agent log files in the apminsightdata/logs directory, which will be present in the process-generated location.


pkgId: 0b8fe07be6207e6e469af2bd47ad28e2946facda5d34a03c270be997b70ce9ba
name: python-apn-validators
arch: src
version: 0.4.1
epoch: 0
release: 1
summary: Tools for validating user data and input in Python.
url: https://pypi.org/project/apn-validators/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apn-validators-0.4.1-1.src.rpm
checksum_type: sha256
description:
  The Validation Package provides a robust and extensible framework for validating data in Python applications. Designed with flexibility and scalability in mind, it offers a suite of pre-built validators and supports custom validation logic. This package ensures data integrity and simplifies the process of enforcing business rules across various domains.




  - **Modular Design**: Each validator is encapsulated in its own class, making it easy to manage and extend the validation logic.
  - **Customizable Error Messages**: Validators support customizable error messages, allowing for clear and context-specific feedback.
  - **Customizable Schema**: Users can create their own validation schemas according to their specific needs, providing flexibility to handle unique validation scenarios.
  - **Comprehensive Validation Suite**: Includes a wide range of validators for common data validation tasks such as length checks, format verification, numerical comparisons, list inclusion, and regular expression matching.



  [Read the documentation](https://agungpn.github.io/apn-validators/)

rpm_license:
  MIT License  Copyright (c) 2024 Agung Prasetyo Nugroho  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


pkgId: 149d441d8b29fcd31cabb785bd35dfd167de56dfa3a6aa825d632572af49473d
name: python-apna-experiment-sdk
arch: src
version: 0.4.10
epoch: 0
release: 1
url: https://pypi.org/project/apna-experiment-sdk/
rpm_license: UNKNOWN
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apna-experiment-sdk-0.4.10-1.src.rpm
checksum_type: sha256
summary:
  This is the SDK for fetching variants in experiments and tracking it to sinks like Bigquery and Mixpanel.


pkgId: 1aa528f345cc15a781d3ec5d6067151510370902b073fea28bfada41bdadc03a
name: python-apnggif
arch: src
version: 0.1.4
epoch: 0
release: 1
summary: Python interface to apng2gif
url: https://github.com/yukinarit/apnggif
rpm_license: zlib/libpng
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apnggif-0.1.4-1.src.rpm
checksum_type: sha256
description:
  Python interface to [apng2gif](https://sourceforge.net/projects/apng2gif/).

  Both APNG (Animated PNG) are GIF are major animation formats nowadays, however there is no good python library to convert from APNG to GIF. `apnggif` is modern, easy-to-use python conversion library (and CLI) built on top of [apng2gif](https://sourceforge.net/projects/apng2gif/) which is written in C++ and also used in [EZGIF.com](https://ezgif.com/).



  `apnggif` requires python>=3.6.

  ```
  pip install apnggif
  ```



  If you pip install apnggif, a command line client `apnggif` is already installed in your system. Try the following command to convert APNG image to GIF.

  ```
  apnggif <GIF> ...
  ```

  For more information about options, check
  ```
  apnggif --help
  ```



  ```python
  from apnggif import apnggif

  apnggif("ball.png")
  ```



  [zlib/libpng License](https://opensource.org/licenses/Zlib).


pkgId: 3a7f5980c3b410a87b03bd9504ffb3c8704bc7cb94a062e2cb46995a66274572
name: python-apns-clerk
arch: src
version: 0.2.0
epoch: 0
release: 1
summary: Python client for Apple Push Notification service (APNs)
url: https://bitbucket.org/aleksihoffman/apns-clerk
rpm_license: Apache 2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apns-clerk-0.2.0-1.src.rpm
checksum_type: sha256
description:
  Python client for `Apple Push Notification service (APNs) <https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html>`_.
  This project is forked from apns-client, as there was a tiny but severe problem with SSL handshake rendering the package to be unusable.
  For the time being please check for apns-client's `documentation <http://apns-client.readthedocs.org>`_ .


pkgId: 0092dd1e7e109732a981fc21fd4210451f3a32ceab139c8a3e3a6962283285f6
name: python-apns-client
arch: src
version: 0.2.1
epoch: 0
release: 1
summary: Python client for Apple Push Notification service (APNs)
url: https://bitbucket.org/sardarnl/apns-client
rpm_license: Apache 2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apns-client-0.2.1-1.src.rpm
checksum_type: sha256
description:
  Python client for `Apple Push Notification service (APNs) <https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html>`_.
  Check `documentation <http://apns-client.readthedocs.org>`_ to learn how to use it.
  Check the client with similar interface for `Google Cloud Messaging <https://pypi.python.org/pypi/gcm-client/>`_.


pkgId: 25d7844c3a75e7faf868ecd35f35dfed29e60b7dea7a361d27de56d18d7e9438
name: python-apogee-connect-rpi
arch: src
version: 1.1.0
epoch: 0
release: 1
summary: Automates data collection with Bluetooth sensors from Apogee Instruments, Inc. using a Raspberry Pi
url: https://pypi.org/project/apogee-connect-rpi/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apogee-connect-rpi-1.1.0-1.src.rpm
checksum_type: sha256
description:
  - [Intro](#prerequisite)
  - [Command Line Application](#command_line_application)
     - [Installation](#installation)
     - [Basic Example](#basic_example)
     - [Commands](#commands)
  - [Python Module API](#python_module_api)
     - [Installation](#installation)
     - [Functions](#functions)
  - [Contact](#contact)



  Apogee Connect for Raspberry Pi is a lightweight script intended for automating the data collection process with Apogee's Bluetooth sensors.

  Apogee Connect can be used in two different ways:

  1. As a standalone command line application with straightforward commands for interacting with Apogee's Bluetooth sensors.

  2. As a Python module for those looking to implement the application functionality directly into their own Python script (The method assumes an existing familiarity with Python, virtual environments, and pip).



  In order to install, you will need pip (the package installer for Python). Pip is likely already included by default on a Raspberry Pi or if Python was installed from source. You can confirm pip is installed by running the command:

  `pip --version`

  If pip isn't already installed, visit the following site for instructions on a simple pip installation:

  [https://pip.pypa.io/en/stable/installation/](https://pip.pypa.io/en/stable/installation/)








  1) We will use pipx, which is a tool that runs Python modules as a standalone command line application. In terminal, run the following command (It may be necessary to close and restart the terminal after installing pipx for it to be recognized):

  `sudo apt install pipx`

  2) Install Apogee Connect for Raspberry Pi from the Python Package Index by running the following command in terminal:

  `pipx install apogee-connect-rpi`

  3) Add the install location to your PATH environment variables so that it can be run from anywhere in the terminal.

  `pipx ensurepath`

  4) Verify installation by running the following command (It may be necessary to close and restart the terminal first for the changes to take effect):

  `apogee --version`



  To update to the latest version, run the following command in terminal:

  `pipx upgrade apogee-connect-rpi`



  To uninstall, run the following command in terminal:

  `pipx uninstall apogee-connect-rpi`



  Note: `apogee` is the command that can be run from the terminal at any location to use the app.

  1. Scan for nearby Apogee sensors to obtain sensor's MAC address required for connection.

  `apogee scan`

  2. Start collecting data from the sensor with the MAC address of AA:BB:CC:DD:EE:FF and set a logging interval of 10 minutes.

  `apogee collect AA:BB:CC:DD:EE:FF --interval 10`

  3. See a list of all currently collecting sensors along with the number of logs currently collected, data file location, start/end time, etc.

  `apogee list`

  4. Stop data collection from the sensor with the MAC address of AA:BB:CC:DD:EE:FF, but not until the epoch timestamp of 1726247820 (Unix timestamp in seconds).

  `apogee stop AA:BB:CC:DD:EE:FF --end 1726247820`



  *Key:*

  `apogee command <required_argument> [--optional_arguments (default: value)]`




  *Usage*

  Collect data from an Apogee sensor and write the data to a csv file.

  Automatic collection is the default collection mode, which will turn the sensor's logging on and then automatically collect all available data and record it in a csv file. The frequency with which the Raspberry Pi will "wake up" and check for available data from all sensors can be adjusted through the `config` command.

  If a manual collection mode is needed, add the `--manual` flag to the command. The first time this command is used for a sensor, the application will turn the sensor's logging on with the included interval, start/end time, etc. Then, use the `collect` command again with the `--manual` flag to collect available data from the sensor and add it to the csv file (when collecting data, the `--file` argument can be included to store the data in a different file than what was originally set but `--interval`, `--start`, and `--end` will be ignored).


  *Examples*

  `apogee collect D7:52:21:95:5C:6C`

  `apogee collect D7:52:21:95:5C:6C  --interval 10 --start 1720647720 --end 1721250915 --file /usr/Documents/greenhouse-data.csv --append`

  `apogee collect D7:52:21:95:5C:6C  --interval 10 --file /usr/Documents/greenhouse-data.csv --manual --overwrite`

  *Documentation*

  `apogee collect <MAC address> [--interval (default: 5)] [--start (default: Now)] [--end (default: Never)] [--file (default: ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv)] [--overwrite] [--append] [--manual]`

  | Argument | Default Value | Usage |
  | -------- | ------------- | ----- |
  | Mac Address| N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF |
  | -i, --interval | 5 minutes | The interval that sensor will log data (must be a positive integer) |
  | -s, --start | Now | Start time for data collection using epoch time (Unix timestamp in seconds) |
  | -e, --end | Never | End time for data collection using epoch time (Unix timestamp in seconds) |
  | -f, --file | ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv | File path to write data to csv file. Will create folders and files if they don’t exist. |
  | -a, --append | False | Add this flag to automatically append to an existing data file. |
  | -o, --overwrite | False | Add this flag to automatically overwrite an existing data file. |
  | -m, --manual | N/A | Add this flag for manual data collection mode.|




  *Usage*

  Change or view the application settings.

  *Examples*

  `apogee config`

  `apogee config --filepath /usr/documents/live-data --precision 4 --temp F --par-filtering True`

  `apogee config --reset`

  *Documentation*

  `apogee config [--filepath (default: ~/Apogee/apogee_connect_rpi/data/)] [--precision (default: 2)] [--temp (default: C)] [--par-filtering (default: False)] [--collection-frequency (default: 5)]`

  | Argument | Default Value | Usage |
  | -------- | ------------- | ----- |
  | (N/A)| (N/A) | If no optional arguments are provided, the current configuration will be displayed. |
  | -f, --filepath | ~/Apogee/apogee-connect-rpi/data/ | The default folder to save collected data. Adjust to avoid needing to specify full filepath every time data collection is initiated. |
  | --p, --precision | 2 | Maximum number of decimal places for collected data. |
  | -t, --temp | C | Change preferred temperature units. Enter “C” for Celsius and “F” for Fahrenheit (without quotations). |
  | -pf, --par-filtering | False | Filter negative PAR (PPFD) values to compensate for sensor "noise" in low-light conditions. Enter "True" or "False" (without quotations). |
  | -cf, --collection-frequency | 5 | Frequency in minutes to check for new data logs and add to .csv data file (This is different than the data logging interval for the sensor). Must be between 5 - 60 minutes. |
  | -r, --reset | N/A | Add this flag to reset configuration back to default values. |



  *Usage*

  List all the sensors currently collecting data.

  *Example*

  `apogee list`

  *Documentation*

  `apogee list`




  *Usage*

  Scan for nearby Apogee sensors

  *Example*

  `apogee scan`

  `apogee scan --time 10`

  *Documentation*

  `apogee scan [--time (default: 5-20)]`

  | Argument | Default Value | Usage |
  | -------- | ------------- | ----- |
  |-t, --time | 5-20 | Number of seconds to scan for Apogee Bluetooth sensors. If not set, scanning will continue until no new sensors are being discovered or a maximum of 20 seconds. |




  *Usage*

  Stop data collection from an Apogee sensor. Can optionally use the `--all` flag to stop data collection from all Apogee sensors instead.

  *Examples*

  `apogee stop D7:52:21:95:5C:6C`

  `apogee stop D7:52:21:95:5C:6C --end 1724428813`

  `apogee stop --all`

  *Documentation*

  `apogee stop <MAC address> [--end (default: now)]`

  | Argument | Default Value | Usage |
  | -------- | ------------- | ----- |
  | MAC address | N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF (Required unless the '--all' flag is used). |
  | -e, --end | Now | End time for data collection using epoch time (Unix timestamp in seconds). Cannot be used when using the '--all' flag. |
  | -a, --all | N/A | Stop data collection for all sensors (Can be used instead of the MAC address requirement).|



  *Usage*

  Transfer data from an Apogee sensor for a desired logging interval. This command is separate from the collect command and is just used to obtain a specific time period or previously logged data.

  *Examples*

  `apogee transfer D7:52:21:95:5C:6C`

  `apogee transfer D7:52:21:95:5C:6C --start 1720647720 --end 1721250915 --file /usr/documents/greenhouse-data.csv --overwrite`

  *Documentation*

  `apogee transfer <MAC address> [--start (default: Now)] [--end (default: Never)] [--file (default: ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv)] [--append] [--overwrite]`

  | Argument | Default Value | Usage |
  | -------- | ------------- | ----- |
  | MAC address | N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF. |
  | -s, --start | Now | Start time for data collection using epoch time (Unix timestamp in seconds) |
  | -e, --end | Never | End time for data collection using epoch time (Unix timestamp in seconds) |
  | -f, --file | ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv | File path to write data to csv file. Will create folders and files is they don’t exist. |
  | -a, --append | N/A | Add this flag to automatically append data to a file if it exists instead of overwriting. |
  | -o, --overwrite | False | Add this flag to automatically overwrite an existing data file. |



  *Usage*

  Show current application version

  *Example*

  `apogee --version`

  *Documentation*

  `apogee --version`




  *Usage*

  Show help menu for entire application or for specific command, depending on usage

  *Example*

  `apogee --help`

  `apogee collect --help`

  *Documentation*

  Entering just `--help` (or `-h`) will show a high-level overview of available commands.
  Entering a command followed by `--help` will show a menu with required and optional arguments.
  Any unrecognized command will also print the high-level overview help menu.






  1) Simply install Apogee Connect for Raspberry Pi from the Python Package Index by running the following command in terminal within an activated virtual environment:

  `pip install apogee-connect-rpi`



  To update to the latest version, run the following command in terminal:

  `pip install apogee-connect-rpi --upgrade`



  To uninstall, run the following command in terminal:

  `pip uninstall apogee-connect-rpi`






  *Usage*

  Collect data from an Apogee sensor and write the data to a csv file.

  Automatic collection is the default collection mode, which will turn the sensor's logging on and then automatically collect all available data and record it in a csv file. The frequency with which the Raspberry Pi will "wake up" and check for available data from all sensors can be adjusted through the `config` function.

  If a manual collection mode is needed, set the `manual` argument to `True`. The first time this function is used for a sensor, the sensor's logging will be turned on with the included interval, start/end time, etc.  Then, use the `collect` function again with `manual=True` to collect available data from the sensor and add it to the csv file (when collecting data, the `file` argument can be included to store the data in a different file than what was originally set but `interval`, `start`, and `end` will be ignored).


  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      await apogee.collect(address="D7:52:21:95:5C:6C", interval=10, file="~/Desktop/greenhouse-data.csv")
  ```

  *Function Signature*

  The values in the function signature are defaults used if the corresponding arguments are omitted.

  ```
  async def collect(
      address,
      interval = 5,
      start = 0,
      end = 4294967295,
      file = '~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv',
      append = False,
      overwrite = False,
      manual = False
  ) -> None

  ```

  *Arguments*

  | Argument | Type | Default Value | Usage |
  | -------- | ---- | ------------- | ----- |
  | address  | str  | N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF |
  | interval | int  | 5 minutes | The interval that sensor will log data (must be a positive integer) |
  | start    | int  | Now | Start time for data collection using epoch time (Unix timestamp in seconds) |
  | end      | int  | Never | End time for data collection using epoch time (Unix timestamp in seconds) |
  | file     | str  | ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv | File path to write data to CSV file. Will create folders and files if they don’t exist. |
  | append   | bool | False | Set to True to automatically append to an existing data file. |
  | overwrite | bool | False | Set to True to automatically overwrite an existing data file. |
  | manual   | bool | False | Set to True for manual data collection mode. |




  *Usage*

  Change or view the application settings. When no arguments are included with the function call, the `config` function will return a dictionary of the current configuration parameters.


  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      await apogee.config(precision=5, filepath="/usr/Documents/GreenhouseData")
      updated_config = await apogee.config()
      print(updated_config)
  ```

  *Function Signature*

  The values in the function signature are defaults used if the corresponding arguments are omitted.

  ```
  async def config(
      precision = 2,
      filepath = '~/Apogee/apogee-connect-rpi/data/',
      temp = 'C',
      par_filtering = False,
      collection_frequency = 5,
      reset = False
  ) -> dict
  ```

  *Arguments*

  | Argument | Type | Default Value | Usage |
  | -------- | ---- | ------------- | ----- |
  | precision  | int  | 2 | Maximum number of decimal places for collected data. |
  | filepath | str  | ~/Apogee/apogee-connect-rpi/data/ | The default folder to save collected data. Adjust to avoid needing to specify full filepath every time data collection is initiated. |
  | temp | str  | C | Change preferred temperature units. Enter “C” for Celsius and “F” for Fahrenheit (without quotations). |
  | par_filtering | bool  | False | Filter negative PAR (PPFD) values to compensate for sensor "noise" in low-light conditions. |
  | collection_frequency | int  | 5 | Frequency in minutes to check for new data logs and add to .csv data file (This is different than the data logging interval for the sensor). Must be between 5 - 60 minutes. |
  | reset | bool | False | Set to True to reset configuration back to default values. |

  *Returns*

  The function returns a dictionary where:

  * *Key:* A string of the configuration parameter name
  * *Value:* The current value of the configuration parameter




  *Usage*

  Retreieve a dictionary of all the sensors currently collecting data.

  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      collecting_sensors = await apogee.list()
      print(collecting_sensors)
  ```

  *Function Signature*

  ```
  async def list() -> dict
  ```

  *Returns*

  The function returns a dictionary where:

  * *Key:* The unique MAC address of the sensor
  * *Value:* A dictionary containing sensor logging information (see table below)


  | Sensor Info Key | Value |
  | -------- | -------- |
  | interval  | int |
  | start_time  | int |
  | end_time  | int |
  | logs  | int |
  | file  | str |
  | sensorID  | int |
  | collection_mode  | str |
  | last_collection_time  | int |



  *Usage*

  Scan for nearby Apogee sensors and retrieve a dictionary with all discovered sensors.

  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      discovered_sensors = await apogee.scan(time=10)
      print(discovered_sensors)
  ```

  *Function Signature*

  The values in the function signature are defaults used if the corresponding arguments are omitted.

  ```
  async def scan(
      time = 20
  ) -> dict
  ```

  *Arguments*

  | Argument | Type | Default Value | Usage |
  | -------- | ----- | ------------- | ----- |
  |time | int | 5-20 | Number of seconds to scan for Apogee Bluetooth sensors. If not set, scanning will continue until no new sensors are being discovered or a maximum of 20 seconds. |

  *Returns*

  The function returns a dictionary where:

  * *Key:* The unique MAC address of the sensor
  * *Value:* A custom Apogee Sensor class with attributes specific to the sensor (see table below)


  | Sensor Attributes  | Value |
  | -------- | -------- |
  | sensorID  | int |
  | alias  | str |
  | serial  | str |
  | type  | str |



  *Usage*

  Stop data collection from an Apogee sensor. Can optionally set the `all` argument to True to stop data collection from all Apogee sensors.

  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      await apogee.stop(address=D7:52:21:95:5C:6C, end=1726247820)
  ```

  *Function Signature*

  The values in the function signature are defaults used if the corresponding arguments are omitted.

  ```
  async def stop(
      address,
      end = {current_time},
      all = False
  )
  ```

  *Arguments*

  | Argument | Type | Default Value | Usage |
  | -------- | ----- | ------------- | ----- |
  | address | str | N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF (Required unless the '--all' flag is used). |
  | end | int | Now | End time for data collection using epoch time (Unix timestamp in seconds). Cannot be used when the 'all' argument is set to True. |
  | all | bool | False | Stop data collection for all sensors (Can be used instead of the address requirement).|




  *Usage*

  Transfer data from an Apogee sensor for a desired logging interval. This function is separate from the collect command and is just used to obtain a specific time period or previously logged data.

  *Example*

  ```
  from apogee_connect_rpi import ApogeeConnect

  async def main():
      apogee = ApogeeConnect()
      await apogee.transfer(address="D7:52:21:95:5C:6C", start=1720647720, end=1726247820)
  ```

  *Function Signature*

  The values in the function signature are defaults used if the corresponding arguments are omitted.

  ```
  async def transfer(
      address,
      start = 0,
      end = 4294967295,
      file = '~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv',
      append = False,
      overwrite = False
  )
  ```

  *Arguments*

  | Argument | Type | Default Value | Usage |
  | -------- | ----- | ------------- | ----- |
  | address | str | N/A (required) | MAC address of sensor in the format of AA:BB:CC:DD:EE:FF. |
  | start | int | Now | Start time for data collection using epoch time (Unix timestamp in seconds) |
  | end | int | Never | End time for data collection using epoch time (Unix timestamp in seconds) |
  | file | str | ~/Apogee/apogee_connect_rpi/data/<MAC_ADDRESS>.csv | File path to write data to csv file. Will create folders and files is they don’t exist. |
  | append | bool | False | Set to True to automatically append data to a file if it exists instead of overwriting. |
  | overwrite | bool | False | Set to True to automatically overwrite an existing data file. |




  For more information or additional help, contact Apogee Instruments at: [techsupport@apogeeinstruments.com](mailto:techsupport@apogeeinstruments.com) or [+1(435)245-8012](tel:+14352458012)

rpm_license:
  MIT License  Copyright (c) 2024 Apogee Instruments, Inc.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


pkgId: 0316f7d7d5ab60837146d79f3fb84ece7246abe946c2b35f04aa615425596e19
name: python-apojwt
arch: src
version: 1.7.0
epoch: 0
release: 1
summary: JWT Authentication Functions and Decorators. Built for Intent's Apogee Microservices
url: https://pypi.org/project/apojwt/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apojwt-1.7.0-1.src.rpm
checksum_type: sha256
description:
  The ApoJWT class has the following constructor:
  ```python
  (
      self,
      secret: str,
      exp_period: int=900,
      iss: str="",
      server_audience: list=[],
      algorithm: str="HS256",
      template=None,
      async_framework: bool=False,
      token_finder=None,
      exception_handler=None)
  """
  Keyword Arguments (those with asterisks are functions):
  JWT Validation
      secret:
          Secret string used to encode and decode the access JWT
      exp_period:
          Length of time in seconds access tokens is valid for.
          Default 900 (15 minutes)
      iss:
          Issuer string used for additional security.
          Default ""
      server_audience:
          Audience names of expected HTTP hosts.
          Audience names are typically base address URLs.
          Ex: https://example.com
      algorithm:
          The algorithm to use when encoding/decoding.
          Default HS256
      admin_permission:
          Optional full admin permission
          JWTs carrying this will always be authorized
  Framework Configuration
      async_framework:
          If True - ApoJWT awaits the decorated function
          (FastAPI needs this True)
          Default: False
      token_finder:
          Returns the Access JWT (framework specific)
          Typically found as the "Authorization" header
          Default: None
          Expected Function Structure:
              (*args, **kwargs) -> str
      exception_handler:
          HTTP error handling function (framework specific)
          Expected Function Structure:
          (code: int, msg: str) -> None
  """
  ```
  <br>
  <br>

rpm_license:
  Copyright 2022 In10t  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


pkgId: 4c16abfc6287dbdefe1d24a617dc9d49909c43b53c1058bec498a353ee43e507
name: python-apollinaire
arch: src
version: 1.3.1
epoch: 0
release: 1
summary: Module for helio- and asteroseismic data analysis.
url: https://gitlab.com/sybreton/apollinaire
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apollinaire-1.3.1-1.src.rpm
checksum_type: sha256
description:
  [![Anaconda-Server Badge](https://anaconda.org/conda-forge/apollinaire/badges/license.svg)](https://anaconda.org/conda-forge/apollinaire)
  [![Anaconda-Server Badge](https://anaconda.org/conda-forge/apollinaire/badges/version.svg)](https://anaconda.org/conda-forge/apollinaire)
  [![Documentation Status](https://readthedocs.org/projects/apollinaire/badge/?version=latest)](https://apollinaire.readthedocs.io/en/latest/?badge=latest)
  [![Static Badge](https://img.shields.io/badge/reference-paper-orange)](https://www.aanda.org/articles/aa/abs/2022/07/aa43330-22/aa43330-22.html)

  Python tools for helioseismic and asteroseismic frameworks.  This package
  provides functions and framework designed for helioseismic and asteroseismic
  instruments data managing and analysis.

  The core of the package is the ``peakbagging`` library, which provides a full
  framework to extract oscillation modes parameters from solar and stellar power
  spectra.





  The apollinaire package core framework is written in Python 3.
  The following Python package are necessary to use ``apollinaire`` :
  - numpy
  - scipy
  - pandas
  - matplotlib
  - h5py
  - emcee
  - corner
  - pathos
  - dill
  - statsmodels
  - numba
  - george
  - astropy
  - tqdm



  With conda:

  `conda install -c conda-forge apollinaire`

  With pip:

  `pip install apollinaire`

  You can also install the most recent version of apollinaire by cloning the GitLab repository:

  `git clone https://gitlab.com/sybreton/apollinaire.git`



  Documentation is available on [Read the Docs](https://apollinaire.readthedocs.io).



  Software development:

  * **Sylvain N. Breton** - Maintainer - (INAF - Osservatorio astrofisico di Catania)

  Contributors are listed below:

  * **Rafael A. García** - Contributor - (CEA Saclay)
  * **Vincent Delsanti** - Contributor - (CentraleSupélec)
  * **Jérôme Ballot** - Contributor - (IRAP - Université de Toulouse)



  If you use ``apollinaire`` in your work, please cite the ``apollinaire`` reference paper
  ([Breton et al., 2022](https://ui.adsabs.harvard.edu/abs/2022A%26A...663A.118B/abstract)),
  the Astrophysics Source Code Library
  [code record](https://ascl.net/2306.022),
  and provide a link to the GitLab repository. You will find more [detailed citing instructions
  in the documentation](https://apollinaire.readthedocs.io/en/latest/usage/citing_apollinaire.html).

rpm_license:
  CeCILL FREE SOFTWARE LICENSE AGREEMENT  Version 2.1 dated 2013-06-21   Notice  This Agreement is a Free Software license agreement that is the result of discussions between its authors in order to ensure compliance with the two main principles guiding its drafting:  * firstly, compliance with the principles governing the distribution of Free Software: access to source code, broad rights granted to users, * secondly, the election of a governing law, French law, with which it is conformant, both as regards the law of torts and intellectual property law, and the protection that it offers to both authors and holders of the economic rights over software.  The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) license are:  Commissariat à l'Ènergie atomique et aux Énergies alternatives - CEA, a public scientific, technical and industrial research establishment, having its principal place of business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.  Centre National de la Recherche Scientifique - CNRS, a public scientific and technological establishment, having its principal place of business at 3 rue Michel-Ange, 75794 Paris cedex 16, France.  Institut National de Recherche en Informatique et en Automatique - Inria, a public scientific and technological establishment, having its principal place of business at Domaine de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex, France.   Preamble  The purpose of this Free Software license agreement is to grant users the right to modify and redistribute the software governed by this license within the framework of an open source distribution model.  The exercising of this right is conditional upon certain obligations for users so as to preserve this status for all subsequent redistributions.  In consideration of access to the source code and the rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors only have limited liability.  In this respect, the risks associated with loading, using, modifying and/or developing or reproducing the software by the user are brought to the user's attention, given its Free Software status, which may make it complicated to use, with the result that its use is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the suitability of the software as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions of security. This Agreement may be freely reproduced and published, provided it is not altered, and that no provisions are either added or removed herefrom.  This Agreement may apply to any or all software for which the holder of the economic rights decides to submit the use thereof to its provisions.  Frequently asked questions can be found on the official website of the CeCILL licenses family (http://www.cecill.info/index.en.html) for any necessary clarification.   Article 1 - DEFINITIONS  For the purpose of this Agreement, when the following expressions commence with a capital letter, they shall have the following meaning:  Agreement: means this license agreement, and its possible subsequent versions and annexes.  Software: means the software in its Object Code and/or Source Code form and, where applicable, its documentation, "as is" when the Licensee accepts the Agreement.  Initial Software: means the Software in its Source Code and possibly its Object Code form and, where applicable, its documentation, "as is" when it is first distributed under the terms and conditions of the Agreement.  Modified Software: means the Software modified by at least one Contribution.  Source Code: means all the Software's instructions and program lines to which access is required so as to modify the Software.  Object Code: means the binary files originating from the compilation of the Source Code.  Holder: means the holder(s) of the economic rights over the Initial Software.  Licensee: means the Software user(s) having accepted the Agreement.  Contributor: means a Licensee having made at least one Contribution.  Licensor: means the Holder, or any other individual or legal entity, who distributes the Software under the Agreement.  Contribution: means any or all modifications, corrections, translations, adaptations and/or new functions integrated into the Software by any or all Contributors, as well as any or all Internal Modules.  Module: means a set of sources files including their documentation that enables supplementary functions or services in addition to those offered by the Software.  External Module: means any or all Modules, not derived from the Software, so that this Module and the Software run in separate address spaces, with one calling the other when they are run.  Internal Module: means any or all Module, connected to the Software so that they both execute in the same address space.  GNU GPL: means the GNU General Public License version 2 or any subsequent version, as published by the Free Software Foundation Inc.  GNU Affero GPL: means the GNU Affero General Public License version 3 or any subsequent version, as published by the Free Software Foundation Inc.  EUPL: means the European Union Public License version 1.1 or any subsequent version, as published by the European Commission.  Parties: mean both the Licensee and the Licensor.  These expressions may be used both in singular and plural form.   Article 2 - PURPOSE  The purpose of the Agreement is the grant by the Licensor to the Licensee of a non-exclusive, transferable and worldwide license for the Software as set forth in Article 5 <#scope> hereinafter for the whole term of the protection granted by the rights over said Software.   Article 3 - ACCEPTANCE  3.1 The Licensee shall be deemed as having accepted the terms and conditions of this Agreement upon the occurrence of the first of the following events:  * (i) loading the Software by any or all means, notably, by downloading from a remote server, or by loading from a physical medium; * (ii) the first time the Licensee exercises any of the rights granted hereunder.  3.2 One copy of the Agreement, containing a notice relating to the characteristics of the Software, to the limited warranty, and to the fact that its use is restricted to experienced users has been provided to the Licensee prior to its acceptance as set forth in Article 3.1 <#accepting> hereinabove, and the Licensee hereby acknowledges that it has read and understood it.   Article 4 - EFFECTIVE DATE AND TERM   4.1 EFFECTIVE DATE  The Agreement shall become effective on the date when it is accepted by the Licensee as set forth in Article 3.1 <#accepting>.   4.2 TERM  The Agreement shall remain in force for the entire legal term of protection of the economic rights over the Software.   Article 5 - SCOPE OF RIGHTS GRANTED  The Licensor hereby grants to the Licensee, who accepts, the following rights over the Software for any or all use, and for the term of the Agreement, on the basis of the terms and conditions set forth hereinafter.  Besides, if the Licensor owns or comes to own one or more patents protecting all or part of the functions of the Software or of its components, the Licensor undertakes not to enforce the rights granted by these patents against successive Licensees using, exploiting or modifying the Software. If these patents are transferred, the Licensor undertakes to have the transferees subscribe to the obligations set forth in this paragraph.   5.1 RIGHT OF USE  The Licensee is authorized to use the Software, without any limitation as to its fields of application, with it being hereinafter specified that this comprises:  1. permanent or temporary reproduction of all or part of the Software by any or all means and in any or all form.  2. loading, displaying, running, or storing the Software on any or all medium.  3. entitlement to observe, study or test its operation so as to determine the ideas and principles behind any or all constituent elements of said Software. This shall apply when the Licensee carries out any or all loading, displaying, running, transmission or storage operation as regards the Software, that it is entitled to carry out hereunder.   5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS  The right to make Contributions includes the right to translate, adapt, arrange, or make any or all modifications to the Software, and the right to reproduce the resulting software.  The Licensee is authorized to make any or all Contributions to the Software provided that it includes an explicit notice that it is the author of said Contribution and indicates the date of the creation thereof.   5.3 RIGHT OF DISTRIBUTION  In particular, the right of distribution includes the right to publish, transmit and communicate the Software to the general public on any or all medium, and by any or all means, and the right to market, either in consideration of a fee, or free of charge, one or more copies of the Software by any means.  The Licensee is further authorized to distribute copies of the modified or unmodified Software to third parties according to the terms and conditions set forth hereinafter.   5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION  The Licensee is authorized to distribute true copies of the Software in Source Code or Object Code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by:  1. a copy of the Agreement,  2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9,  and that, in the event that only the Object Code of the Software is redistributed, the Licensee allows effective access to the full Source Code of the Software for a period of at least three years from the distribution of the Software, it being understood that the additional acquisition cost of the Source Code shall not exceed the cost of the data transfer.   5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE  When the Licensee makes a Contribution to the Software, the terms and conditions for the distribution of the resulting Modified Software become subject to all the provisions of this Agreement.  The Licensee is authorized to distribute the Modified Software, in source code or object code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by:  1. a copy of the Agreement,  2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9,  and, in the event that only the object code of the Modified Software is redistributed,  3. a note stating the conditions of effective access to the full source code of the Modified Software for a period of at least three years from the distribution of the Modified Software, it being understood that the additional acquisition cost of the source code shall not exceed the cost of the data transfer.   5.3.3 DISTRIBUTION OF EXTERNAL MODULES  When the Licensee has developed an External Module, the terms and conditions of this Agreement do not apply to said External Module, that may be distributed under a separate license agreement.   5.3.4 COMPATIBILITY WITH OTHER LICENSES  The Licensee can include a code that is subject to the provisions of one of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the Modified or unmodified Software, and distribute that entire code under the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL.  The Licensee can include the Modified or unmodified Software in a code that is subject to the provisions of one of the versions of the GNU GPL, GNU Affero GPL and/or EUPL and distribute that entire code under the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL.   Article 6 - INTELLECTUAL PROPERTY   6.1 OVER THE INITIAL SOFTWARE  The Holder owns the economic rights over the Initial Software. Any or all use of the Initial Software is subject to compliance with the terms and conditions under which the Holder has elected to distribute its work and no one shall be entitled to modify the terms and conditions for the distribution of said Initial Software.  The Holder undertakes that the Initial Software will remain ruled at least by this Agreement, for the duration set forth in Article 4.2 <#term>.   6.2 OVER THE CONTRIBUTIONS  The Licensee who develops a Contribution is the owner of the intellectual property rights over this Contribution as defined by applicable law.   6.3 OVER THE EXTERNAL MODULES  The Licensee who develops an External Module is the owner of the intellectual property rights over this External Module as defined by applicable law and is free to choose the type of agreement that shall govern its distribution.   6.4 JOINT PROVISIONS  The Licensee expressly undertakes:  1. not to remove, or modify, in any manner, the intellectual property notices attached to the Software;  2. to reproduce said notices, in an identical manner, in the copies of the Software modified or not.  The Licensee undertakes not to directly or indirectly infringe the intellectual property rights on the Software of the Holder and/or Contributors, and to take, where applicable, vis-�-vis its staff, any and all measures required to ensure respect of said intellectual property rights of the Holder and/or Contributors.   Article 7 - RELATED SERVICES  7.1 Under no circumstances shall the Agreement oblige the Licensor to provide technical assistance or maintenance services for the Software.  However, the Licensor is entitled to offer this type of services. The terms and conditions of such technical assistance, and/or such maintenance, shall be set forth in a separate instrument. Only the Licensor offering said maintenance and/or technical assistance services shall incur liability therefor.  7.2 Similarly, any Licensor is entitled to offer to its licensees, under its sole responsibility, a warranty, that shall only be binding upon itself, for the redistribution of the Software and/or the Modified Software, under terms and conditions that it is free to decide. Said warranty, and the financial terms and conditions of its application, shall be subject of a separate instrument executed between the Licensor and the Licensee.   Article 8 - LIABILITY  8.1 Subject to the provisions of Article 8.2, the Licensee shall be entitled to claim compensation for any direct loss it may have suffered from the Software as a result of a fault on the part of the relevant Licensor, subject to providing evidence thereof.  8.2 The Licensor's liability is limited to the commitments made under this Agreement and shall not be incurred as a result of in particular: (i) loss due the Licensee's total or partial failure to fulfill its obligations, (ii) direct or consequential loss that is suffered by the Licensee due to the use or performance of the Software, and (iii) more generally, any consequential loss. In particular the Parties expressly agree that any or all pecuniary or business loss (i.e. loss of data, loss of profits, operating loss, loss of customers or orders, opportunity cost, any disturbance to business activities) or any or all legal proceedings instituted against the Licensee by a third party, shall constitute consequential loss and shall not provide entitlement to any or all compensation from the Licensor.   Article 9 - WARRANTY  9.1 The Licensee acknowledges that the scientific and technical state-of-the-art when the Software was distributed did not enable all possible uses to be tested and verified, nor for the presence of possible defects to be detected. In this respect, the Licensee's attention has been drawn to the risks associated with loading, using, modifying and/or developing and reproducing the Software which are reserved for experienced users.  The Licensee shall be responsible for verifying, by any or all means, the suitability of the product for its requirements, its good working order, and for ensuring that it shall not cause damage to either persons or properties.  9.2 The Licensor hereby represents, in good faith, that it is entitled to grant all the rights over the Software (including in particular the rights set forth in Article 5 <#scope>).  9.3 The Licensee acknowledges that the Software is supplied "as is" by the Licensor without any other express or tacit warranty, other than that provided for in Article 9.2 <#good-faith> and, in particular, without any warranty as to its commercial value, its secured, safe, innovative or relevant nature.  Specifically, the Licensor does not warrant that the Software is free from any error, that it will operate without interruption, that it will be compatible with the Licensee's own equipment and software configuration, nor that it will meet the Licensee's requirements.  9.4 The Licensor does not either expressly or tacitly warrant that the Software does not infringe any third party intellectual property right relating to a patent, software or any other property right. Therefore, the Licensor disclaims any and all liability towards the Licensee arising out of any or all proceedings for infringement that may be instituted in respect of the use, modification and redistribution of the Software. Nevertheless, should such proceedings be instituted against the Licensee, the Licensor shall provide it with technical and legal expertise for its defense. Such technical and legal expertise shall be decided on a case-by-case basis between the relevant Licensor and the Licensee pursuant to a memorandum of understanding. The Licensor disclaims any and all liability as regards the Licensee's use of the name of the Software. No warranty is given as regards the existence of prior rights over the name of the Software or as regards the existence of a trademark.   Article 10 - TERMINATION  10.1 In the event of a breach by the Licensee of its obligations hereunder, the Licensor may automatically terminate this Agreement thirty (30) days after notice has been sent to the Licensee and has remained ineffective.  10.2 A Licensee whose Agreement is terminated shall no longer be authorized to use, modify or distribute the Software. However, any licenses that it may have granted prior to termination of the Agreement shall remain valid subject to their having been granted in compliance with the terms and conditions hereof.   Article 11 - MISCELLANEOUS   11.1 EXCUSABLE EVENTS  Neither Party shall be liable for any or all delay, or failure to perform the Agreement, that may be attributable to an event of force majeure, an act of God or an outside cause, such as defective functioning or interruptions of the electricity or telecommunications networks, network paralysis following a virus attack, intervention by government authorities, natural disasters, water damage, earthquakes, fire, explosions, strikes and labor unrest, war, etc.  11.2 Any failure by either Party, on one or more occasions, to invoke one or more of the provisions hereof, shall under no circumstances be interpreted as being a waiver by the interested Party of its right to invoke said provision(s) subsequently.  11.3 The Agreement cancels and replaces any or all previous agreements, whether written or oral, between the Parties and having the same purpose, and constitutes the entirety of the agreement between said Parties concerning said purpose. No supplement or modification to the terms and conditions hereof shall be effective as between the Parties unless it is made in writing and signed by their duly authorized representatives.  11.4 In the event that one or more of the provisions hereof were to conflict with a current or future applicable act or legislative text, said act or legislative text shall prevail, and the Parties shall make the necessary amendments so as to comply with said act or legislative text. All other provisions shall remain effective. Similarly, invalidity of a provision of the Agreement, for any reason whatsoever, shall not cause the Agreement as a whole to be invalid.   11.5 LANGUAGE  The Agreement is drafted in both French and English and both versions are deemed authentic.   Article 12 - NEW VERSIONS OF THE AGREEMENT  12.1 Any person is authorized to duplicate and distribute copies of this Agreement.  12.2 So as to ensure coherence, the wording of this Agreement is protected and may only be modified by the authors of the License, who reserve the right to periodically publish updates or new versions of the Agreement, each with a separate number. These subsequent versions may address new issues encountered by Free Software.  12.3 Any Software distributed under a given version of the Agreement may only be subsequently distributed under the same version of the Agreement or a subsequent version, subject to the provisions of Article 5.3.4 <#compatibility>.   Article 13 - GOVERNING LAW AND JURISDICTION  13.1 The Agreement is governed by French law. The Parties agree to endeavor to seek an amicable solution to any disagreements or disputes that may arise during the performance of the Agreement.  13.2 Failing an amicable solution within two (2) months as from their occurrence, and unless emergency proceedings are necessary, the disagreements or disputes shall be referred to the Paris Courts having jurisdiction, by the more diligent Party.


pkgId: ec7f7ef18bbe326ea07360b9b2115f2676ce56eb04f4985b05e88e85b027220f
name: python-apollo
arch: src
version: 4.2.13
epoch: 0
release: 1
summary: Apollo API library
url: https://github.com/galaxy-genome-annotation/python-apollo
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apollo-4.2.13-1.src.rpm
checksum_type: sha256
description:
  Apollo is a Python library for interacting with
  `Apollo <https://github.com/gmod/apollo/>`__. Arrow is its companion CLI tool.
      `for the record <https://gitter.im/galaxy-genome-annotation/Lobby?at=5ebee6c049a1b7318479380d>`__ ... arrow makes working with Apollo SOOO much easier
      — Nathan Dunn, Apollo Developer


pkgId: 8c184173f5149af96a291475ee7d4f266c7b5bead362dad29186d2f59ce858c9
name: python-apollo-fpga
arch: src
version: 1.1.1
epoch: 0
release: 1
summary: Python library and host tools for Apollo FPGA debug controllers.
url: https://pypi.org/project/apollo-fpga/
rpm_license: BSD
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apollo-fpga-1.1.1-1.src.rpm
checksum_type: sha256
description:
  Apollo is the on-board debugger and programmer on [Cynthion](https://greatscottgadgets.com/cynthion/). It is used to load gateware over USB onto Cynthion's FPGA. Alternatively it may be used as an on-board or external debugger for certain other FPGA platforms.

  Apollo consists of two main parts: firmware for the on-board debug microcontroller and Python-based software for the host computer.



  If you have a Cynthion, Apollo host software is installed automatically as a dependency when you [install Cynthion software](https://cynthion.readthedocs.io/en/latest/getting_started.html#cynthion-host-software-installation).

  To explicitly install the apollo-fpga Python module and the `apollo` command-line tool or to upgrade them to the latest version run:
  ```
  pip install --upgrade apollo-fpga
  ```



  To upgrade Apollo firmware on a Cynthion it is typically not necessary to compile the firmware yourself. Instead follow [Upgrading Cynthion Device Firmware](https://cynthion.readthedocs.io/en/latest/getting_started.html#updating-cynthion-microcontroller-firmware-and-fpga-configuration-flash).

  To compile and install onto Cynthion run:

  ```
  $ cd apollo/firmware
  $ make APOLLO_BOARD=cynthion get-deps dfu
  ```

  This will download dependencies, compile the firmware, and install it onto Cynthion with [pyfwup](https://github.com/greatscottgadgets/pyfwup) and [Saturn-V](https://github.com/greatscottgadgets/saturn-v).

  Alternatively you can use variables to specify a different board or a fixed hardware revision:

  ```
  $ cd apollo/firmware
  $ make APOLLO_BOARD=cynthion BOARD_REVISION_MAJOR=1 BOARD_REVISION_MINOR=4 get-deps dfu
  ```

  A specified revision will override automatic hardware revision detection which is supported on Cynthion r0.6 and above. You must specify the revision to compile firmware for a Cynthion older than r0.6.

  Once installation is complete, LED A should activate, indicating that Apollo is running.


pkgId: 6b64d8c3f13851ac407764b2046dcf5017b856869b55b65380bfabf0e08f380a
name: python-apollo-lunar
arch: src
version: 2024.5.21
epoch: 0
release: 1
summary: A Python SDK/CLI for Lunar API
url: https://pypi.org/project/apollo-lunar/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apollo-lunar-2024.5.21-1.src.rpm
checksum_type: sha256
description:
  - [Description](#description)
  - [Installation](#installation)
  - [How to use](#how-to-use)
    - [credentials](#credentials)
    - [SDK](#sdk)
    - [CLI](#cli)
  - [Development](#development)
    - [Requirements for development](#requirements-for-development)
    - [Python environment](#python-environment)
    - [Documents generation](#documents-generation)


  SDK for lunar microservices



  A Python based SDK & CLI for Lunar microservices

  This python package includes
  - Functions for [lunar-rec](https://github.com/sktaiflow/lunar-rec)
  - CLI (Command Line Interface) named `lunar`



  Build package and wheel files with `poetry`.

  ```bash

  poetry install

  poetry run lunar -h
  ```




  `lunar` uses a credentials file to setup a config(`env`, `apikey`) with a `init` file type.
  The path for credentials file and format must be as follow:

  ```bash
  [Path]

  .lunar/credentials
  ```

  ```bash
  [default]
  env=local
  apikey=ABCDEFG123

  [dev]
  env=dev
  apikey=AERSDF123

  [stg]
  env=stg
  apikey=HIJKLMNO456

  [prd]
  env=prd
  apikey=PQRSTUV789
  ```
  The first [default] is used when SDK / CLI is run without a specified profile.

  To specify a profile, use `AWS_PROFILE` to overrides the default profile for commands that run in that session.

  ```bash
  export AWS_PROFILE=stg
  ```



  `lunar` package provides functions for using all lunar microservices.
  For the detailed information related to each function, please refer to [Docs](https://sktaiflow.github.io/lunar-sdk/lunar/).

  Here is an example for CRUDing channels:

  ```python
  import lunar


  client = lunar.client("channel")


  channels = client.get_channels()


  channel = client.get_channel(id="test_channel")


  channel = client.create_channel(id="new_channel", experiment_id="new_experiment")


  updated_channel = client.update_channel(id="test_channel", experiment_id="a")
  partially_updated_channel = client.update_channel_partial(id="test_channel", experiment_id="ab")


  client.delete_channel(channel.id)
  ```

  All functions on the SDK also supports asyncio. If necessary, use an async version of each function.
  - Naming rule: Suffix with **_async**. (e.g. `list_channels()` → `list_channels_async()`)



  `lunar` also provides CLI (Command Line Interface) generated by [click](https://click.palletsprojects.com/) package.

  ```zsh
  poetry run lunar -h
  ```

  should show something like the below:

  ``` zsh
  Usage: lunar [OPTIONS] COMMAND [ARGS]...

  Options:
    -h, --help  Show this message and exit.

  Commands:
    channel     Channels on lunar-rec
    experiment  Experiments on lunar-rec
    recommend   Recommendation on lunar-rec
  ```

  Each option also has its own usage instruction.

  ``` zsh
  poetry run lunar experiments -h

  Usage: lunar experiment [OPTIONS] {CREATE|LIST|READ|UPDATE|DELETE}

    Experiments on lunar-rec

    Send a request to Lunar Recommendation API (`/v1/experiemnts/`).

    Return: dict or list(dict)

  Options:
    -i, --id TEXT                   Unique identifier of a channel
    -b, --buckets <TEXT INTEGER>...
                                    Bucket list
    -s, --bucketing-seed TEXT       Random seed for bucketing
    -p, --partial                   Partial update or not (default: False)
    -h, --help                      Show this message and exit.
  ```





  - Python 3.8
  - Poetry

  Installing Python 3.8 with [pyenv](https://github.com/pyenv/pyenv) is recommended. Check [this](https://github.com/pyenv/pyenv#installation).

  For zsh,

  ```zsh

  xcode-select --install


  brew update
  brew install pyenv openssl readline sqlite3 xz zlib

  echo 'eval "$(pyenv init --path)"' >> ~/.zshrc
  eval '"$(pyenv init -)"' >> ~/.zshrc




  pyenv install 3.8.10
  pyenv global 3.8.10




  python -V


  curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
  poetry config virtualenvs.in-project true
  ```

  should work.



  ```zsh
  poetry install
  ```

  If you need additional deps,

  ```zsh

  poetry add package_name


  poetry add --dev package_name
  ```



  ```bash
  rm -rf docs
  pdoc --html --config show_source_code=False -f -o ./docs lunar
  ```


pkgId: 7ba9cb5e5c90d0156f531daef4dc4b95ad593108ac8d93ac4d9b822665e84d1b
name: python-apollo-orm
arch: src
version: 2.2.3
epoch: 0
release: 1
summary: ORM Cassandra/Scylla Stable Version (2.x.x)
url: https://github.com/daniloarodrigues/apollo-orm
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apollo-orm-2.2.3-1.src.rpm
checksum_type: sha256
description:
  This is the Apollo ORM library for Cassandra/Scylla, a Python library that provides an Object-Relational Mapping (ORM) interface to ScyllaDB.



  You can install the Apollo ORM library via pip:

  ```bash
  pip install apollo-orm
  ```



  Here's a basic example of how to use the Apollo ORM library:

  ```python
  from apollo_orm.orm.core import ORMInstance
  from apollo_orm.domains.models.entities.connection_config.entity import ConnectionConfig
  from apollo_orm.orm.credentials.credential_service import CredentialService

  data = {
      "id": "1",
      "name": "John Doe",
      "age": 30
  }


  credentials = CredentialService(
      ["localhost1", "localhost2", "localhost3"],
      9042,
      "user",
      "password",
      "keyspace_name"
  ).credential()


  connection_config = ConnectionConfig(
      credentials,
      ["table_name1", "table_name2"])


  orm_instance = ORMInstance(connection_config)


  orm_instance.insert(data, "table_name1")


  results = orm_instance.select(data, "table_name1")


  for result in results:
      print(result)
  ```



  Contributions are welcome! Please open an issue if you encounter any problems, or a pull request if you have improvements to suggest.



  This project is licensed under the MIT License.
  ```
  MIT License

  Copyright (c) 2023 Danilo Rodrigues

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.


pkgId: fe5a26abb5ece42dbe311a655278e52c2ff78a15960e301a880b157170064abc
name: python-apolo-cli
arch: src
version: 24.11.2
epoch: 0
release: 1
summary: Apolo Platform client
url: https://github.com/neuro-inc/platform-client-python
rpm_license: Apache 2
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apolo-cli-24.11.2-1.src.rpm
checksum_type: sha256
description:
  [![codecov](https://codecov.io/gh/neuro-cli/platform-client-python/branch/master/graph/badge.svg)](https://codecov.io/gh/neuro-cli/platform-client-python)



  Welcome to Apolo CLI tool for https://apolo.us/.
  Package ship command line tool called `apolo`. With it you can:
  * Execute and debug jobs
  * Manipulate Data
  * Make some fun



  For OSX users install coreutils to properly interpret shell commands:

  ```
  brew install coreutils
  ```

  Before you begin, it is recommended to have clean virtual environment installed:

  ```shell
  python -m venv .env
  source .env/bin/activate
  ```

  Development flow:

  * Install dependencies: `make setup`
  * Reformat code: `make format`
  * Lint: `make lint`
  * Run tests: `make test`
  * Run end-to-end tests: `make e2e`


pkgId: e318016e55ac0ae0af1331e9e724c8eb208ae2ef90734d26f72be1b6c7fce184
name: python-apolo-flow
arch: src
version: 24.10.3
epoch: 0
release: 1
summary: Pipelines system for Apolo MLOps platform
url: https://github.com/neuro-inc/neuro-flow
rpm_license: Apache 2
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apolo-flow-24.10.3-1.src.rpm
checksum_type: sha256
description:
  `apolo-flow` is a tool that simplifies daily jobs on the Apolo platform.

  Apolo Flow provides users the ability to create YAML files that configure routine things, for example, starting a Jupiter Notebook on the platform, starting a training pipeline, opening a file browser for remote storage, etc.

  The tool builds required Docker images and starts all necessary Apolo jobs under the hood, while the user doesn't have to type all required `apolo` options and manually manage the pipeline scenarios.



  The tool is hosted on PyPI and can be installed by `pip` as a regular Python project:

  ```bash
  $ pip install apolo-flow
  ```

  Use the `--upgrade` option to upgrading Apolo Flow to the latest version:

  ```bash
  $ pip install --upgrade apolo-flow
  ```


pkgId: 8dc96f1f5414dda4179b48bf0d73d5262be1b59466b5a4281fe853065ff3ffbf
name: python-apolo-sdk
arch: src
version: 24.11.2
epoch: 0
release: 1
summary: Apolo SDK
url: https://github.com/neuro-inc/platform-client-python
rpm_license: Apache 2
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apolo-sdk-24.11.2-1.src.rpm
checksum_type: sha256
description:
  [![codecov](https://codecov.io/gh/neuro-inc/platform-client-python/branch/master/graph/badge.svg)](https://codecov.io/gh/neuro-inc/platform-client-python)



  Welcome to Python Apolo-SDK for https://console.apolo.us/.



  https://apolo-sdk.readthedocs.io/en/latest/




  Install from PyPI:

  ```shell
  $ pip install apolo-sdk
  ```



  For OSX users install coreutils to properly interpret shell commands:

  ```
  brew install coreutils
  ```

  Before you begin, it is recommended to have clean virtual environment installed:

  ```shell
  $ python -m venv .env
  $ source .env/bin/activate
  ```

  Development flow:

  * Install dependencies: `make setup`
  * Reformat code: `make format`
  * Lint: `make lint`
  * Run tests: `make test`


pkgId: 2f22830c2c116b9a99da8be0ad738b3f307435af18dbff4147343547e21791b8
name: python-apologies
arch: src
version: 0.1.50
epoch: 0
release: 1
summary: Python code to play a game similar to Sorry
url: https://pypi.org/project/apologies/
rpm_license: Apache-2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apologies-0.1.50-1.src.rpm
checksum_type: sha256
description:
  [![pypi](https://img.shields.io/pypi/v/apologies.svg)](https://pypi.org/project/apologies/)
  [![license](https://img.shields.io/pypi/l/apologies.svg)](https://github.com/pronovic/apologies/blob/master/LICENSE)
  [![wheel](https://img.shields.io/pypi/wheel/apologies.svg)](https://pypi.org/project/apologies/)
  [![python](https://img.shields.io/pypi/pyversions/apologies.svg)](https://pypi.org/project/apologies/)
  [![Test Suite](https://github.com/pronovic/apologies/workflows/Test%20Suite/badge.svg)](https://github.com/pronovic/apologies/actions?query=workflow%3A%22Test+Suite%22)
  [![docs](https://readthedocs.org/projects/apologies/badge/?version=stable&style=flat)](https://apologies.readthedocs.io/en/stable/)
  [![coverage](https://coveralls.io/repos/github/pronovic/apologies/badge.svg?branch=master)](https://coveralls.io/github/pronovic/apologies?branch=master)
  [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)

  [Apologies](https://github.com/pronovic/apologies) is a Python library that implements a game similar to the [Sorry](https://en.wikipedia.org/wiki/Sorry!_(game)) board game.  On UNIX-like platforms, it includes a console demo that plays the game with automated players, intended for use by developers and not by end users.  See the [documentation](https://apologies.readthedocs.io/en/stable) for notes about the public interface.

  It was written as a learning exercise and technology demonstration effort, and
  serves as a complete example of how to manage a modern (circa 2020) Python
  project, including style checks, code formatting, integration with IntelliJ, CI
  builds at GitHub, and integration with PyPI and Read the Docs.


pkgId: bd611e031d49b9e05bb1a526b80283c58f5f051f6c27dd0dacc298006862c970
name: python-apoor
arch: src
version: 1.3.1
epoch: 0
release: 1
summary: A small personal package created to store code and data I often reuse.
url: https://github.com/a-poor/apoor
rpm_license: GNU General Public License v3 (GPLv3)
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apoor-1.3.1-1.src.rpm
checksum_type: sha256
description:
  ![PyPI - Status](https://img.shields.io/pypi/status/apoor)
  ![PyPI](https://img.shields.io/pypi/v/apoor)
  [![Build Status](https://travis-ci.org/a-poor/apoor.svg?branch=master)](https://travis-ci.org/a-poor/apoor)
  [![codecov](https://codecov.io/gh/a-poor/apoor/branch/master/graph/badge.svg)](https://codecov.io/gh/a-poor/apoor)
  [![Scrutinizer code quality (GitHub/Bitbucket)](https://img.shields.io/scrutinizer/quality/g/a-poor/apoor/master)](https://scrutinizer-ci.com/g/a-poor/apoor/?branch=master)
  ![PyPI - License](https://img.shields.io/pypi/l/apoor)
  [![Documentation Status](https://readthedocs.org/projects/apoor/badge/?version=latest)](https://apoor.readthedocs.io/en/latest/?badge=latest)


  _created by Austin Poor_

  A small personal package created to store code and data I often reuse.

  Inspired by Hillary Parker's [blog post](https://hilaryparker.com/2013/04/03/personal-r-packages/) about personal R packages.

  I'm also using it to



  * GitHub Repo: https://github.com/a-poor/apoor
  * PyPi Package: https://pypi.org/project/apoor
  * Documentation: https://apoor.readthedocs.io/en/latest



  ```bash
  $ pip install apoor
  ```

  ***

  I'd love to know if you use this package or have any suggestions for things to add!


pkgId: d6e37cf294e4cb7aa5c33290e7f9d4fda350f3751f2dc24c7311852556acf209
name: python-apophenia
arch: src
version: 0.1.2
epoch: 0
release: 1
summary: Extract and structure all the data from a Git repository to make them usable in RAG.
url: https://pypi.org/project/apophenia/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apophenia-0.1.2-1.src.rpm
checksum_type: sha256
description:
  ![Build](https://github.com/4383/apophenia/actions/workflows/main.yml/badge.svg)
  ![PyPI](https://img.shields.io/pypi/v/apophenia.svg)
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apophenia.svg)
  ![PyPI - Status](https://img.shields.io/pypi/status/apophenia.svg)
  [![Downloads](https://pepy.tech/badge/apophenia)](https://pepy.tech/project/apophenia)
  [![Downloads](https://pepy.tech/badge/apophenia/month)](https://pepy.tech/project/apophenia/month)

  Apophenia give meaning to any existing Git repository.

  Apophenia extract and structure all the data from a Git repository to make
  them usable in RAG or in with AI agents.

  Apophenia impose a meaningful interpretation on a nebulous stimulus (a Git
  repo).



  Extract data from a given repository:

  ```bash
  $ pip install apophenia
  $ apophenia https://github.com/4383/niet \
    --faiss_path /tmp/results.faiss \
    --metadata_path /tmp/results.json
  ```

  And use generated data in a RAG (python snippet example):

  ```python
  import faiss
  import json
  import numpy as np
  from sentence_transformers import SentenceTransformer
  from transformers import AutoModelForCausalLM, AutoTokenizer


  def load_index_and_metadata(faiss_path, metadata_path):
      index = faiss.read_index(faiss_path)
      with open(metadata_path, 'r', encoding='utf-8') as f:
          metadata = json.load(f)
      return index, metadata


  def embed_query(query, model):
      return model.encode(query, convert_to_tensor=True).cpu().numpy()


  def search_in_faiss(index, query_embedding, metadata, k=5):
      distances, indices = index.search(np.array([query_embedding]), k)
      results = []
      for i, idx in enumerate(indices[0]):
          result = metadata[idx]
          result['distance'] = distances[0][i]
          results.append(result)
      return results


  def build_prompt(query, retrieved_info):
      prompt = f"Answer the following question based on the retrieved information:\n\n"
      prompt += f"Question: {query}\n\n"
      prompt += "Retrieved Information:\n"
      for info in retrieved_info:
          content_type = info.get("type", "unknown")
          content_preview = info.get("content_preview", "No preview available")
          prompt += f"- {content_type.upper()}: {content_preview}\n"
      prompt += "\nYour Answer:"
      return prompt


  def generate_response(prompt, model_name="EleutherAI/gpt-neo-125M", max_length=200):
      tokenizer = AutoTokenizer.from_pretrained(model_name)
      model = AutoModelForCausalLM.from_pretrained(model_name)
      input_ids = tokenizer(prompt, return_tensors="pt").input_ids
      output = model.generate(input_ids, max_length=max_length)
      return tokenizer.decode(output[0], skip_special_tokens=True)


  def run_rag_system(query, faiss_path, metadata_path, embedding_model_name, generative_model_name):
      
      index, metadata = load_index_and_metadata(faiss_path, metadata_path)
      embedding_model = SentenceTransformer(embedding_model_name)

      query_embedding = embed_query(query, embedding_model)

      
      retrieved_info = search_in_faiss(index, query_embedding, metadata)

      prompt = build_prompt(query, retrieved_info)

      response = generate_response(prompt, model_name=generative_model_name)

      return response, retrieved_info

  if __name__ == "__main__":
      
      FAISS_PATH = "results.faiss"
      METADATA_PATH = "results.json"
      EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
      GENERATIVE_MODEL_NAME = "EleutherAI/gpt-neo-125M"

      query = "How does the authentication system work in this repository?"

      response, retrieved_info = run_rag_system(
          query=query,
          faiss_path=FAISS_PATH,
          metadata_path=METADATA_PATH,
          embedding_model_name=EMBEDDING_MODEL_NAME,
          generative_model_name=GENERATIVE_MODEL_NAME
      )

      print("Generated Response:")
      print(response)
      print("\nRetrieved Information:")
      for info in retrieved_info:
          print(info)
  ```



  Here’s a **list of potential applications** for using the generated results
  (FAISS vectors and JSON metadata) within a **RAG
  (Retrieval-Augmented Generation)** system:


  - Generate enriched answers by combining documentation, commit messages, and code.
  - Example questions:
    - *"How do I use the `authenticate_user` function?"*
    - *"What is the structure of this project?"*


  - Quickly search for specific parts of the code or documentation.
  - Identify relevant functions or files based on queries like:
    - *"Where is the authentication logic implemented?"*
    - *"Which module handles network connections?"*


  - Retrieve historical information to understand bugs or errors.
  - Analyze recent changes with queries like:
    - *"What are the latest modifications in this file?"*
    - *"Which commits mention this bug?"*


  - Create a changelog based on commit messages and their diffs.
  - Example use case:
    - Automatically generate a structured changelog for a new release.


  - Identify outdated dependencies or technologies.
  - Plan migrations by answering queries like:
    - *"Which files are using Eventlet?"*
    - *"Which commits introduced asyncio?"*


  - Search for changes related to vulnerabilities or critical dependencies.
  - Example questions:
    - *"Which files use OpenSSL?"*
    - *"Which commits fixed vulnerabilities?"*


  - Generate guides or technical manuals from existing code and documentation fragments.
  - Example:
    - Create an installation guide from README files and configuration scripts.


  - Understand individual contributions or file evolution.
  - Example questions:
    - *"Who wrote this function?"*
    - *"What are John Doe's contributions?"*


  - Search for specific concepts within the project:
    - *"Where is the caching logic handled?"*
    - *"Which files mention secure connections?"*


  - Simplify onboarding for new developers:
    - Provide guided answers like:
      - *"The main features of this project are documented in `README.md`."*
      - *"`auth.py` handles authentication logic."*


  - Identify which files or functions are impacted by a specific commit.
  - Example questions:
    - *"Which files were modified by this commit?"*
    - *"Which tests are affected by this change?"*


  - Extract code examples from existing fragments in files or commits.
  - Example use case:
    - Generate a snippet to illustrate how to use a specific function or module.


  - Quickly find useful information to solve a technical issue.
  - Example questions:
    - *"Which file is responsible for this exception?"*
    - *"Which commit introduced this error?"*


  - Identify the libraries used and their versions.
  - Example questions:
    - *"Which version of Django is being used?"*
    - *"Which commits mention outdated dependencies?"*


  - Search for changes related to performance optimization.
  - Example questions:
    - *"Which commits optimized this file?"*
    - *"Which functions were refactored for better performance?"*


  - Identify team members who are most active in certain areas of the project.
  - Example questions:
    - *"Who contributes the most to the networking module?"*
    - *"What are the primary files in this project?"*


  - Create customized reports on the state or evolution of a project.
  - Examples:
    - Report on the 10 most significant recent commits.
    - List of main modules and the most modified files.


  - Integrate extracted data into CI/CD pipelines.
  - Example:
    - Identify critical files for a specific build task.


  - Compare versions of files or branches using diffs and commits.


  - Identify areas of the code that need documentation or refactoring.
  - Example questions:
    - *"Which files lack associated documentation?"*
    - *"Which commits mention suboptimal code?"*

  If you recognize yourself in one of these examples then Apophenia is for you.



  You can use generated output FAISS with [langchain](
  https://python.langchain.com/docs/integrations/vectorstores/faiss/)
  or with any modern libraries like [llamaindex](
  https://docs.llamaindex.ai/en/stable/api_reference/storage/vector_store/faiss/)

  - https://github.com/facebookresearch/faiss
  - https://pypi.org/project/faiss/


pkgId: c2390dab6e4e167adaa07208e3fcea1da55c1604ac32841fb9b850aba39fa6ba
name: python-aporia
arch: src
version: 3.31.0
epoch: 0
release: 1
summary: please add a summary manually as the author left a blank one
url: https://pypi.org/project/aporia/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-aporia-3.31.0-1.src.rpm
checksum_type: sha256
description:
  Aporia's Python SDK is a powerful tool designed to streamline ML monitoring and observability.

  Define your models, monitors, dashboards, segments, custom metrics, and other ML Observability resources *as code*, just like in Terraform or Pulumi. The SDK also enables you to query metrics from Aporia to integrate with other platforms.



   * **ML Monitoring as Code:** Make it easier to manage and track changes by managing your models, dashboards, segments, and other ML Observability resources as code.
   * **CI/CD Integration:** Integrate with your CI/CD pipeline to automatically monitor all your models with Aporia.
   * **Query Metrics:** Fetch metrics directly from Aporia's platform to inform decisions or to use in other applications.
   * **Data Source Integration:** You can define and integrate multiple types of data sources, like S3, Snowflake, Glue Data Catalog, Databricks, and others. This allows your models to leverage a wide range of data for training and inference.
   * **Pythonic Interface:** Use the familiar Python programming paradigm to interact with Aporia.




  You can install the Aporia SDK using pip:

  ```bash
  pip install aporia
  ```





  ```python
  import datetime
  import os

  from aporia import Aporia, MetricDataset, MetricParameters, TimeRange
  import aporia.as_code as aporia

  aporia_token = os.environ["APORIA_TOKEN"]
  aporia_account = os.environ["APORIA_ACCOUNT"]
  aporia_workspace = os.environ["APORIA_WORKSPACE"]

  stack = aporia.Stack(
      token=aporia_token,
      account=aporia_account,
      workspace=aporia_workspace,
  )



  stack.apply(yes=True, rollback=False, config_path="config.json")
  ```



  This example shows how you can use the Aporia SDK to query metrics from a model:

  ```python
  from datetime import datetime
  from aporia import (
      Aporia,
      MetricDataset,
      MetricParameters,
      TimeRange,
      DatasetType,
  )

  aporia_token = os.environ["APORIA_TOKEN"]
  aporia_account = os.environ["APORIA_ACCOUNT"]
  aporia_workspace = os.environ["APORIA_WORKSPACE"]

  aporia_client = Aporia(
      token=aporia_token,
      account_name=aporia_account,
      workspace_name=aporia_workspace,
  )

  last_week_dataset = MetricDataset(
      dataset_type=DatasetType.SERVING,
      time_range=TimeRange(
          start=datetime.now() - datetime.timedelta(days=7),
          end=datetime.now(),
      ),
  )

  metrics = aporia_client.query_metrics(
      model_id=model_id,
      metrics=[
          MetricParameters(
              dataset=MetricDataset(dataset_type=DatasetType.SERVING),
              name="count",
          ),
      ],
  )

  print(f"The model had {metrics[0]} predictions last week")
  ```

  Refer to the official Aporia documentation for detailed information on how to use the SDK.


  This package uses Poetry for dependency management. To install dependencies, use:

  ```bash
  poetry install
  ```


pkgId: 926e1c2ed4838ae44b7cc875bc5ca35955a8a604103d0420c058b46641c0e5e9
name: python-apos
arch: src
version: 0.2.1.2
epoch: 0
release: 1
summary: The backbone for message-driven applications.
url: https://github.com/mkossatz/apos
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-apos-0.2.1.2-1.src.rpm
checksum_type: sha256
description:
  The backbone for message-driven applications.


  This Python library is designed to act as a software-level message broker, enabling a lightweight implementation of the publish-subscribe design pattern.

  apos was born to accomplish two objectives:
  * Decouple the application layer from any interfaces
  * Develop reactive business functions

  With apos, you can develop a message-driven application. This means that commands, events, and queries are sent to apos, which in return executes the functions that subscribe to these messages. This means that an adapter providing an external interface, may it be a web-API or a CLI, would not directly call application functions, but would rather send a message to apos, which will in return execute the business functions that subscribe to these messages. Equally, a business function would not call any other business function, but rather publishes an event, which other business functions can subscribe to and execute upon, controlled through apos.

  ![](https://miro.medium.com/max/652/1*ZTxTLfH0FWRIQLAZFlBGEQ.png)


  See the Medium article linked below to read about why this library was created and how it is intended to be used.
  https://mkossatz.medium.com/a-backbone-for-message-driven-applications-ffdcef67824c



  The library can be found on PyPi:
  https://pypi.org/project/apos/


  ```shell
  pip3 install apos
  ```



  The code below is a very lightweight example of how you can use apos for commands, queries, and events.

  ```python

  from apos import apos


  class RegisterUserCommand:
      pass


  class UserRegisteredEvent:
      pass


  class NewUserGreetedEvent:
      pass


  class RetrieveUserQuery:
      pass


  class RetrieveUserResponse:
      pass


  def register_user(command: RegisterUserCommand) -> None:
      
      apos.publish_event(
          UserRegisteredEvent())


  def greet_new_user(event: UserRegisteredEvent) -> None:
      
      apos.publish_event(
          NewUserGreetedEvent())


  def retrieve_user(query: RetrieveUserQuery) -> RetrieveUserResponse:
      
      return RetrieveUserResponse()



  apos.subscribe_command(RegisterUserCommand, register_user)
  apos.subscribe_event(UserRegisteredEvent, [greet_new_user])
  apos.subscribe_query(RetrieveUserQuery, retrieve_user)


  apos.publish_command(RegisterUserCommand())
  events = apos.get_published_events()
  print(events)
  response: RetrieveUserResponse = apos.publish_query(RetrieveUserQuery())
  print(response)


  ```





  You can find examples in the examples directory of the projects repository.
  https://github.com/mkossatz/apos/tree/main/examples


pkgId: 55b070b44d431e7e38f04d74ac1159497642b4fc092293237bdc3a8ddc35cd97
name: python-app-json-file-cache
arch: src
version: 0.2.2
epoch: 0
release: 1
summary: Simple user directory respecting JSON cache for expensive functions.
url: https://github.com/JohannesEbke/app_json_file_cache
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-json-file-cache-0.2.2-1.src.rpm
checksum_type: sha256
description:
  Provides a function decorator that caches the return value in a JSON file in the appropriate application cache directory.
  It requires all function parameters and return values to be encodeable to JSON, so that the cache is human-readable.
  It supports a "vary" guard value (e.g. a data model version) that protects against using old versions of cache.
  Additionally, a cheaper default function (e.g. returning shipped, precalculated values) can be specified as a stand-in
  until an explicit recalculate function is called.


pkgId: ed82f791bea6da4bbaf2c0b92e6b0af36b69529a0387a8d7137ca273c6e32149
name: python-app-model
arch: src
version: 0.3.0
epoch: 0
release: 1
summary: Generic application schema implemented in python
url: https://pypi.org/project/app-model/
rpm_license: BSD 3-Clause License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-model-0.3.0-1.src.rpm
checksum_type: sha256
description:
  [![License](https://img.shields.io/pypi/l/app-model.svg?color=green)](https://github.com/pyapp-kit/app-model/raw/main/LICENSE)
  [![PyPI](https://img.shields.io/pypi/v/app-model.svg?color=green)](https://pypi.org/project/app-model)
  [![Python Version](https://img.shields.io/pypi/pyversions/app-model.svg?color=green)](https://python.org)
  [![CI](https://github.com/pyapp-kit/app-model/actions/workflows/ci.yml/badge.svg)](https://github.com/pyapp-kit/app-model/actions/workflows/ci.yml)
  [![codecov](https://codecov.io/gh/pyapp-kit/app-model/branch/main/graph/badge.svg)](https://codecov.io/gh/pyapp-kit/app-model)
  [![Documentation Status](https://readthedocs.org/projects/app-model/badge/?version=latest)](https://app-model.readthedocs.io/en/latest/?badge=latest)

  Generic application schema implemented in python.

  This is a schema for declarative organization of application data, such as
  menus, keybindings, actions/commands, etc...

  Inspired by the VS-Code application model

  docs at https://app-model.readthedocs.io/en/latest/


pkgId: 4a4a87b32a0518f9b8532513b8d00a9aaf0005c934b534769e9d043ddf608bbd
name: python-app-monitoring
arch: src
version: 1.1.0
epoch: 0
release: 1
summary: A to monitoring my app.
url: http://gitea:8000/datlt4/fortifai_monitoring
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-monitoring-1.1.0-1.src.rpm
checksum_type: sha256
description:
  ```bash
  python3 setup.py sdist bdist_wheel
  ```



  ```bash
  twine upload dist/*
  ```



  ```bash
  docker run -it --rm -v $(pwd):/workspace/app_monitoring python:3.12.7-alpine /bin/sh

  apk update
  cd /workspace/app_monitoring
  python3 -m pip install setuptools
  python3 setup.py sdist bdist_wheel
  apk add twine
  apk add gcc python3-dev musl-dev linux-headers
  TWINE_USERNAME="__token__" TWINE_PASSWORD="pypi-....................................................." twine upload --non-interactive dist/*
  ```


pkgId: b632394a6213dae0eb174296cc423d71af890933b5ded4b0c9d31be425c6d572
name: python-app-server
arch: src
version: 0.9.11
epoch: 0
release: 1
summary: a lightweight web application launcher for gunicorn and static files.
url: https://github.com/viur-framework/viur-app_server
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-server-0.9.11-1.src.rpm
checksum_type: sha256
description:
  The App Server project allows to start a gunicorn server to deliver application logic. Which is wrapped by a [werkzeug](https://werkzeug.palletsprojects.com/) server to deliver staticfiles.
  Primarily the App Server is used as a lightweight alternative to the [dev_appserver](https://cloud.google.com/appengine/docs/standard/python3/testing-and-deploying-your-app?hl=de#local-dev-server) for the google appengine build with python.
  But all [gunicorn](https://gunicorn.org/) projects can be started with it.
  The static paths are stored in an app.yaml as handlers and are therefore compatible to google appengine projects.


  Take a look at the example folder. Here you can find a start.sh which starts the App Server on port 8080.


  App Server allows app code to connect to external APIs, eg [Google Cloud Datastore](https://cloud.google.com/datastore/docs/), like normal. To use the [local datastore emulator](https://cloud.google.com/datastore/docs/tools/datastore-emulator), first start it, then in a separate shell [set the appropriate environment variables](https://cloud.google.com/datastore/docs/tools/datastore-emulator#setting_environment_variables) (notably `DATASTORE_EMULATOR_HOST` and `DATASTORE_DATASET`) to point your app to it before you run App Server.


  The app server dependents on the following packages
  * [werkzeug](https://werkzeug.palletsprojects.com/)
  * [pyyaml](https://pyyaml.org/wiki/PyYAMLDocumentation)
  * [gunicorn](https://gunicorn.org/)


  use pip or pipenv to install this package
   ```sh
      pip install app-server
     ```



  Distributed under the MIT License. See `LICENSE` for more information.


pkgId: bc84bacb0a7250e9a46b32b52affd6846e336e5cd8c6a118a438287646171c48
name: python-app-settings-dict
arch: src
version: 0.6.4
epoch: 0
release: 1
summary: A dictionary to manage settings for apps.
url: https://github.com/wheelercj/app_settings
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-settings-dict-0.6.4-1.src.rpm
checksum_type: sha256
description:
  ![Tests](https://github.com/wheelercj/app_settings/actions/workflows/tests.yaml/badge.svg)

  A dictionary-like Settings class for managing an application's settings.

  Install with `pip install app-settings-dict`.
  Import with `from app_settings_dict import Settings`.

  See examples of use in [the test file](https://github.com/wheelercj/app_settings/blob/main/tests/test_app_settings_dict.py) and in [zk-ssg](https://github.com/wheelercj/zk-ssg/blob/main/ssg/settings.py)'s settings file.


pkgId: 517fc927277dc6df7f51964169506879a1451f2d9c8f1213f7dfe0b67beb49e8
name: python-app-skellington
arch: src
version: 0.2.0
epoch: 0
release: 1
summary: app_skellington CLI framework
url: https://pypi.org/project/app-skellington/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-skellington-0.2.0-1.src.rpm
checksum_type: sha256
description:
  Application framework for Python, features include:
   - Pain-free multi-level command menu: Expose public class methods as commands available to user.
   - Simple to define services and automatic dependency injection based on name (with custom invocation as an option). \*WIP
   - INI-style config and and validation (provided through ConfigObj).
   - Colored logging (provided through colorlog)
   - Works on Linux, Windows, and Mac.
  Principles:
   - Lend to creating beautiful, easy to read and understand code in the application.
   - Minimize coupling of applications to this framework.
   - Compatible with Linux, Windows, and Mac. Try to be compatible as possible otherwise.
   - Try to be compatible with alternate Python runtimes such as PyPy and older python environments. \*WIP

  https://pypi.org/project/app-skellington/

  Site configurations are supported through ConfigObj. There is a config.spec
  in the src directory which is a validation file; it contains the accepted
  parameter names, types, and limits for configurable options in the
  application which is built on app_skellington. The format is multi-level .ini syntax.
  Reference the ConfigObj documentation for config.ini and config.spec
  format. See:
   - https://configobj.readthedocs.io/en/latest/configobj.html#the-config-file-format
   - https://configobj.readthedocs.io/en/latest/configobj.html#validation
  Config files (config.ini) are created if they don't exist. The
  file always contains the full specification of parameters; i.e. even default
  parameters are added into the config file.
  Linux:
   * /home/\<user\>/.config/\<app_name\>/config.ini
   * /home/\<user\>/.cache/\<app_name\>/log/\<app_name\>.log
  Windows:
   * C:\\Users\\\<user>\\\<app_name\>\\Local\\\<app_name\>\\config.ini
   * C:\\Users\\\<user>\\\<app_name\>\\Local\\\<app_name\>\\Logs\\\<app_name\>.log
  Application configuration can be overridden ad-hoc through the --config <filename>
  argument.

  Set 'APPSKELLINGTON_ENABLE_LOGGING' environment variable to any value which turns
  on AppSkellington-level logging. For example,
      APPSKELLINGTON_DEBUG=1 <executable>
  or
      export APPSKELLINGTON_DEBUG=1
      <executable>

  Tests are a WIP. Recommendation is to run 'pytest' in the 'tests' directory.

  I recommend pyenv to install a reliable, controlled python of preferred version locally.
  ```
  curl https://pyenv.run | bash

  tee -a "$HOME"/.profile <<'EOF'
  export PYENV_ROOT="$HOME/.pyenv"
  [[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
  eval "$(pyenv init -)"
  EOF
  ```
   * reference https://github.com/pyenv/pyenv
  Clone the repo:
  ```commandline
  git clone https://git-mirror.zavage.net/zavage-software/app_skellington.git
  ```
  Install pre-commit hooks:
  ```commandline
  pre-commit install
  ```
  Build:
  ```
  python -m build
  ```
  Install:
  ```
  pip install .
  ```
  Formatting and Linters:
  ```
  black app_skellington
  isort app_skellington
  flake8 app_skellington
  ```
  Publish:
  ```

  git push

  git tag -a v0.2.0 -m "0.2.0 provides modern pyproject.toml build with setuptools, versioning, and publishing"
  git push origin v0.2.0

  python -m build

  twine check dist/*
  twine upload dist/*
  ```
   * Reference https://packaging.python.org/en/latest/overview/

  setuptools_scm will infer the version based on the latest tag in your Git history.
  Ensure you are tagging your commits with meaningful version numbers like v1.0.0, v1.1.0, etc.

  MIT no attribution required - https://opensource.org/license/mit-0
   * Allows commercial use.
   * Allows modifications and closed-source derivatives.
   * Fully interoperable with nearly all other open-source licenses, including GPL (when combined properly).

   * Project page: https://zavage-software.com/portfolio/app_skellington
   * Please report bugs, improvements, or feedback!
   * Contact: mathew@zavage.net
   * Packing and distribution conforms to PEP 621 https://peps.python.org/pep-0621/
   * Reference https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/

rpm_license:
  App Skellington  MIT No Attribution  Copyright (c) 2024 Mathew Guest  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN `NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


pkgId: 0af1fe6a01db8bb427c0fbc8ec6f6868fd60d5829a66965b470feada6205fc74
name: python-app-store-download-count-badge-maker
arch: src
version: 0.2.2
epoch: 0
release: 1
summary: A command-line tool to create badges displaying the number of app downloads from App Store
url: https://github.com/nnsnodnb/app-store-download-count-badge-maker
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-store-download-count-badge-maker-0.2.2-1.src.rpm
checksum_type: sha256
description:
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

  [![PyPI Package version](https://badge.fury.io/py/app-store-download-count-badge-maker.svg)](https://pypi.org/project/app-store-download-count-badge-maker)
  [![Python Supported versions](https://img.shields.io/pypi/pyversions/app-store-download-count-badge-maker.svg)](https://pypi.org/project/app-store-download-count-badge-maker)
  [![format](https://img.shields.io/pypi/format/app-store-download-count-badge-maker.svg)](https://pypi.org/project/app-store-download-count-badge-maker)
  [![implementation](https://img.shields.io/pypi/implementation/app-store-download-count-badge-maker.svg)](https://pypi.org/project/app-store-download-count-badge-maker)
  [![LICENSE](https://img.shields.io/pypi/l/app-store-download-count-badge-maker.svg)](https://pypi.org/project/app-store-download-count-badge-maker)


  A command-line tool to create badges displaying the number of app downloads from App Store



  ```shell
  $ pip install app-store-download-count-badge-maker
  ```

  or

  ```shell
  $ pipx install app-store-download-count-badge-maker
  ```



  - Python 3.9 or later



  ```shell
  $ app-store-download-count-badge-maker generate \
    --config config.yml \
    --output dist
  ```

  By default, the `--config (or -c)` option is set to `config.yml` and the `--output (or -o)` options is set to `dist`.

  > [!NOTE]
  > The count is based on 3 days prior to the execution date.



  Create a configuration file in YAML format.
  The recommended name is `config.yml`.



  The configuration file `config.yml` should contain the following sections:

  - `secrets`: This section holds the credentials required to access the App Store Connect API.
    - `private_key`: Path to the private key file (e.g., `private.p8`). The private key must have access **Finance**.
    - `issuer_id`: The issuer ID from App Store Connect.
    - `key_id`: The key ID from App Store Connect.
    - `vendor_number`: The vendor number associated with your App Store account. [View payments and proceeds](https://developer.apple.com/help/app-store-connect/getting-paid/view-payments-and-proceeds)
  - `apps`: A list of applications for which you want to create download count badges.
    - `apple_identifier`: The unique identifier for the app in the App Store.
    - `frequency`: The frequency at which you want to generate the badge. Must be one of `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`.
    - `badge_style` (Optional): The style of the badge. Must be one of `flat` (default), `flat-square`, `plastic`, `for-the-badge`, `social`.



  ```yaml
  secrets:
    private_key: private.p8
    issuer_id: 12345678-1234-1234-1234-123456789012
    key_id: 12345678
    vendor_number: 12345678
  apps:
    - apple_identifier: 1289764391
      frequency: MONTHLY
    - apple_identifier: 1234567890
      frequency: WEEKLY
      badge_style: flat-square
  ```



  This tool uses [Shields.io](https://shields.io/) to create badges displaying the number of app downloads from App Store.



  |  Frequency  | Badge Style                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
  |:-----------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
  |   YEARLY    | ![year-flat](https://img.shields.io/badge/download-1.2k%2Fyear-brightgreen?style=flat&cacheSeconds=31536000) ![year-flat-square](https://img.shields.io/badge/download-123%2Fyear-green?style=flat-square&cacheSeconds=31536000) ![year-plastic](https://img.shields.io/badge/download-99%2Fyear-yellowgreen?style=plastic&cacheSeconds=31536000) ![year-for-the-badge](https://img.shields.io/badge/download-9%2Fyear-yellow?style=for-the-badge&cacheSeconds=31536000) ![year-social](https://img.shields.io/badge/download-0%2Fyear-yellow?style=social&cacheSeconds=31536000)           |
  |   MONTHLY   | ![month-flat](https://img.shields.io/badge/download-1.2k%2Fmonth-brightgreen?style=flat&cacheSeconds=31536000) ![month-flat-square](https://img.shields.io/badge/download-123%2Fmonth-green?style=flat-square&cacheSeconds=31536000) ![month-plastic](https://img.shields.io/badge/download-99%2Fmonth-yellowgreen?style=plastic&cacheSeconds=31536000) ![month-for-the-badge](https://img.shields.io/badge/download-9%2Fmonth-yellow?style=for-the-badge&cacheSeconds=31536000) ![month-social](https://img.shields.io/badge/download-0%2Fmonth-yellow?style=social&cacheSeconds=31536000) |
  |   WEEKLY    | ![week-flat](https://img.shields.io/badge/download-1.2k%2Fweek-brightgreen?style=flat&cacheSeconds=31536000) ![week-flat-square](https://img.shields.io/badge/download-123%2Fweek-green?style=flat-square&cacheSeconds=31536000) ![week-plastic](https://img.shields.io/badge/download-99%2Fweek-yellowgreen?style=plastic&cacheSeconds=31536000) ![week-for-the-badge](https://img.shields.io/badge/download-9%2Fweek-yellow?style=for-the-badge&cacheSeconds=31536000) ![week-social](https://img.shields.io/badge/download-0%2Fweek-yellow?style=social&cacheSeconds=31536000)           |
  |    DAILY    | ![day-flat](https://img.shields.io/badge/download-1.2k%2Fday-brightgreen?style=flat&cacheSeconds=31536000) ![day-flat-square](https://img.shields.io/badge/download-123%2Fday-green?style=flat-square&cacheSeconds=31536000) ![day-plastic](https://img.shields.io/badge/download-99%2Fday-yellowgreen?style=plastic&cacheSeconds=31536000) ![day-for-the-badge](https://img.shields.io/badge/download-9%2Fday-yellow?style=for-the-badge&cacheSeconds=31536000) ![day-social](https://img.shields.io/badge/download-0%2Fday-yellow?style=social&cacheSeconds=31536000)                     |



  - [nnsnodnb/self-app-store-download-count-badges](https://github.com/nnsnodnb/self-app-store-download-count-badges)



  This software is licensed under the MIT License.


pkgId: 9b7c53de0a97d7705296d3717917e93f7ccfb51edb287ba57daec14d48e3437e
name: python-app-store-server-library
arch: src
version: 1.5.0
epoch: 0
release: 1
summary: The App Store Server Library
url: https://pypi.org/project/app-store-server-library/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-store-server-library-1.5.0-1.src.rpm
checksum_type: sha256
description:
  The Python server library for the [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi) and [App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications). Also available in [Swift](https://github.com/apple/app-store-server-library-swift), [Node.js](https://github.com/apple/app-store-server-library-node), and [Java](https://github.com/apple/app-store-server-library-java).


  1. [Installation](#installation)
  2. [Documentation](#documentation)
  3. [Usage](#usage)
  4. [Support](#support)





  - Python 3.8+


  ```sh
  pip install app-store-server-library
  ```



  [Documentation](https://apple.github.io/app-store-server-library-python/)

  [WWDC Video](https://developer.apple.com/videos/play/wwdc2023/10143/)



  To use the App Store Server API or create promotional offer signatures, a signing key downloaded from App Store Connect is required. To obtain this key, you must have the Admin role. Go to Users and Access > Integrations > In-App Purchase. Here you can create and manage keys, as well as find your issuer ID. When using a key, you'll need the key ID and issuer ID as well.



  Download and store the root certificates found in the Apple Root Certificates section of the [Apple PKI](https://www.apple.com/certificateauthority/) site. Provide these certificates as an array to a SignedDataVerifier to allow verifying the signed data comes from Apple.





  ```python
  from appstoreserverlibrary.api_client import AppStoreServerAPIClient, APIException
  from appstoreserverlibrary.models.Environment import Environment

  private_key = read_private_key("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") # Implementation will vary

  key_id = "ABCDEFGHIJ"
  issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55"
  bundle_id = "com.example"
  environment = Environment.SANDBOX

  client = AppStoreServerAPIClient(private_key, key_id, issuer_id, bundle_id, environment)

  try:
      response = client.request_test_notification()
      print(response)
  except APIException as e:
      print(e)
  ```



  ```python
  from appstoreserverlibrary.models.Environment import Environment
  from appstoreserverlibrary.signed_data_verifier import VerificationException, SignedDataVerifier

  root_certificates = load_root_certificates()
  enable_online_checks = True
  bundle_id = "com.example"
  environment = Environment.SANDBOX
  app_apple_id = None # appAppleId must be provided for the Production environment
  signed_data_verifier = SignedDataVerifier(root_certificates, enable_online_checks, environment, bundle_id, app_apple_id)

  try:
      signed_notification = "ey.."
      payload = signed_data_verifier.verify_and_decode_notification(signed_notification)
      print(payload)
  except VerificationException as e:
      print(e)
  ```



  ```python
  from appstoreserverlibrary.api_client import AppStoreServerAPIClient, APIException, GetTransactionHistoryVersion
  from appstoreserverlibrary.models.Environment import Environment
  from appstoreserverlibrary.receipt_utility import ReceiptUtility
  from appstoreserverlibrary.models.HistoryResponse import HistoryResponse
  from appstoreserverlibrary.models.TransactionHistoryRequest import TransactionHistoryRequest, ProductType, Order

  private_key = read_private_key("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") # Implementation will vary

  key_id = "ABCDEFGHIJ"
  issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55"
  bundle_id = "com.example"
  environment = Environment.SANDBOX

  client = AppStoreServerAPIClient(private_key, key_id, issuer_id, bundle_id, environment)
  receipt_util = ReceiptUtility()
  app_receipt = "MI.."

  try:
      transaction_id = receipt_util.extract_transaction_id_from_app_receipt(app_receipt)
      if transaction_id != None:
          transactions = []
          response: HistoryResponse = None
          request: TransactionHistoryRequest = TransactionHistoryRequest(
              sort=Order.ASCENDING,
              revoked=False,
              productTypes=[ProductType.AUTO_RENEWABLE]
          )
          while response == None or response.hasMore:
              revision = response.revision if response != None else None
              response = client.get_transaction_history(transaction_id, revision, request, GetTransactionHistoryVersion.V2)
              for transaction in response.signedTransactions:
                  transactions.append(transaction)
          print(transactions)
  except APIException as e:
      print(e)

  ```



  ```python
  from appstoreserverlibrary.promotional_offer import PromotionalOfferSignatureCreator
  import time

  private_key = read_private_key("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") # Implementation will vary

  key_id = "ABCDEFGHIJ"
  bundle_id = "com.example"

  promotion_code_signature_generator = PromotionalOfferSignatureCreator(private_key, key_id, bundle_id)

  product_id = "<product_id>"
  subscription_offer_id = "<subscription_offer_id>"
  application_username = "<application_username>"
  nonce = "<nonce>"
  timestamp = round(time.time()*1000)
  base64_encoded_signature = promotion_code_signature_generator.create_signature(product_id, subscription_offer_id, application_username, nonce, timestamp)
  ```




  Include the optional async dependency
  ```sh
  pip install app-store-server-library[async]
  ```


  ```python
  from appstoreserverlibrary.api_client import AsyncAppStoreServerAPIClient, APIException
  from appstoreserverlibrary.models.Environment import Environment

  private_key = read_private_key("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") # Implementation will vary

  key_id = "ABCDEFGHIJ"
  issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55"
  bundle_id = "com.example"
  environment = Environment.SANDBOX

  client = AsyncAppStoreServerAPIClient(private_key, key_id, issuer_id, bundle_id, environment)

  try:
      response = await client.request_test_notification()
      print(response)
  except APIException as e:
      print(e)


  await client.async_close()
  ```



  Only the latest major version of the library will receive updates, including security updates. Therefore, it is recommended to update to new major versions.


pkgId: ada22ac1a8211ff262e7de50e2c3da17c85010797485e1c39668ee2e177f2d30
name: python-app-tools-zxw
arch: src
version: 1.0.82
epoch: 0
release: 1
summary: 更新： 微信支付SDK - 分离V1和V2两个版本, V1版本需要手动更改库中config.py文件的配置
url: https://github.com/sunshineinwater/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-tools-zxw-1.0.82-1.src.rpm
checksum_type: sha256
description:
  -
      1. 项目根目录下必须包含config.py文件

  - 支付接口编写规范：
    支付服务_代码规范.py



  - 1.0.69 : 更新 Errors.py中的异常码
  - 1.0.70 : 阿里云 异步短信验证码接口.  注意: 阿里云SDK需单独安装, 安装包在需要用的功能文件中列出
  - 1.0.71 : 调整异步短信接口命名
    - SMS_发送短息ASYNC --> SMS_发送短信v2.py
    - "SMS_发送短信v2", 合并同步短信接口,统一使用阿里V2 SDK.
  - 1.0.72 : 新增: msvc_user_auth 手机登录\注册相关 interface , api
  - 1.0.73 : msvc_user_auth  interface异常返回,少嵌套一层detail
  - 1.0.80 : 更改： 微信支付SDK - 分离V1和V2两个版本, V1版本需要手动更改库中config.py文件的配置
  - 1.0.81 : 修改: config.py文件中的 支付宝配置为路径
  - 1.0.82 : ERROR code 增加: 数据查询失败, 数据增加失败, 数据删除失败, 数据更新失败


pkgId: 08661a02ca08d8d65ed738378752ff333709718f255601185d0dbff951f1a848
name: python-app-tools-zxw
arch: src
version: 1.0.83
epoch: 0
release: 1
summary: 更新： 微信支付SDK - 分离V1和V2两个版本, V1版本需要手动更改库中config.py文件的配置
url: https://github.com/sunshineinwater/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-tools-zxw-1.0.83-1.src.rpm
checksum_type: sha256
description:
  -
      1. 项目根目录下必须包含config.py文件

  - 支付接口编写规范：
    支付服务_代码规范.py



  - 1.0.69 : 更新 Errors.py中的异常码
  - 1.0.70 : 阿里云 异步短信验证码接口.  注意: 阿里云SDK需单独安装, 安装包在需要用的功能文件中列出
  - 1.0.71 : 调整异步短信接口命名
    - SMS_发送短息ASYNC --> SMS_发送短信v2.py
    - "SMS_发送短信v2", 合并同步短信接口,统一使用阿里V2 SDK.
  - 1.0.72 : 新增: msvc_user_auth 手机登录\注册相关 interface , api
  - 1.0.73 : msvc_user_auth  interface异常返回,少嵌套一层detail
  - 1.0.80 : 更改： 微信支付SDK - 分离V1和V2两个版本, V1版本需要手动更改库中config.py文件的配置
  - 1.0.81 : 修改: config.py文件中的 支付宝配置为路径
  - 1.0.82 : ERROR code 增加: 数据查询失败, 数据增加失败, 数据删除失败, 数据更新失败
  - 1.0.83 : Funcs/fastapi_logger.py 优化,可自主设置logger文件名


pkgId: c4bc9005b6e1ef3f3d424f8f4a5148f5b0fb9c3e35c877a75617a5db47a03560
name: python-app-transport-framework-library
arch: src
version: 0.1.12
epoch: 0
release: 1
summary: please add a summary manually as the author left a blank one
url: https://pypi.org/project/app-transport-framework-library/
rpm_license: UNKNOWN
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app-transport-framework-library-0.1.12-1.src.rpm
checksum_type: sha256
description:
  Die App-Transport-Framework (ATF) Library ist eine Python-Bibliothek zur Verarbeitung und Handhabung von ATF-Nachrichten (<https://simplifier.net/app-transport-framework>) im FHIR-Format. Die Bibliothek bietet eine einfache Möglichkeit, verschiedene Anwendungsfälle und ihre zugehörigen Handler zu registrieren und zu verwalten.



  Installieren Sie diese Bibliothek mit dem folgenden Befehl:

  ```
  pip install app-transport-framework-library
  ```



  1. Importieren Sie die benötigten Klassen und Funktionen aus der Bibliothek:

  ```python
  from app_transport_framework_library.atf_bundle_processor import ATF_BundleProcessor
  ```

  2. Erstellen Sie eine Instanz des ATF_BundleProcessor und registrieren Sie die Handler für die Anwendungsfälle, die Sie erweitern möchten:

  ```python
  atf_processor = ATF_BundleProcessor(sender, source)

  atf_processor.register_use_case_handler(
      "https://gematik.de/fhir/atf/CodeSystem/operation-identifier-cs",
      "eRezept_Rezeptanforderung;Rezeptanfrage",
      eRezept_Rezeptanforderung_RezeptanfrageHandler()
  )

  atf_processor.register_use_case_handler(
      "https://gematik.de/fhir/atf/CodeSystem/service-identifier-cs",
      "eRezept_Rezeptanforderung;Rezeptanfrage_Storno",
      eRezept_Rezeptanforderung_Rezeptanfrage_StornoHandler()
  )

  ```

  3. Events

  Abonnieren Sie das 'message_to_send_event', um die generierten ausgehenden Eingangsbestätigungen zu erhalten und weiterzuleiten:

  ```python
  from app_transport_framework_library.models.message_to_send import MessageToSend

  def on_message_to_send_triggered(message_to_send: MessageToSend):
      
      pass


  atf_processor.message_to_send_event.subscribe(on_message_to_send_triggered)

  ```

  Abonnieren Sie das 'received_Empfangsbestaetigung_event', um die eingehenden Eingangsbestätigungen des belieferten Adressaten zu erhalten und die Message-ID der gesendeten Nachricht auszuwerten:

  ```python
  from app_transport_framework_library.models.empfangsbestaetigung import Empfangsbestaetigung

  def on_received_Empfangsbestaetigung(empfangsbestaetigung: Empfangsbestaetigung):
      print(f"'{receiver_address}' hat Empfangsbestätigung für '{empfangsbestaetigung.message_id}' von '{empfangsbestaetigung.sender}' erhalten")

  atf_processor.received_Empfangsbestaetigung_event.subscribe(on_received_Empfangsbestaetigung)
  ```

  Abonnieren Sie das 'focus_Ressource_to_process_event', um die inhaltlichen Ressourcen des Anwendungsfalls zu erhalten und auswerten zu können:

  ```python
  from app_transport_framework_library.models.bundle_focus_content import BundleFocusContent
  from fhir.resources.R4B.communication import Communication

  def on_focus_Ressource_to_process(bundle_content: BundleFocusContent):
      from app_transport_framework_library.models.bundle_focus_content import BundleFocusContent
  from fhir.resources.R4B.communication import Communication

  def on_focus_Ressource_to_process(bundle_content: BundleFocusContent):
      print(f"Verarbeitung des Bundles mit Fokus auf '{bundle_content.code}'")
      if bundle_content.code == "Selbsttest;Lieferung":
          com_parsed = Communication.parse_raw(bundle_content.bundle_entries[0].json())
          decoded_message = base64.b64decode(com_parsed.payload[0].contentAttachment.data)

          print(f"Das Bundle enthält {len(bundle_content.bundle_entries)} Einträge")
          print(f"Die Kommunikation enthält die Payload '{decoded_message.decode('utf-8')}'")

  atf_processor.focus_Ressource_to_process_event.subscribe(on_focus_Ressource_to_process)

  ```



  Eine Beispielimplementierung kann hier eingesehen werden:
  <https://github.com/gematik/api-app-transport-framework/tree/main/src/poc>



  Dieses Projekt steht unter der MIT-Lizenz - siehe die LICENSE-Datei für Details.


pkgId: 2629bb05f5d25f8b7d47b59ff9d918544cf1ad68f21be2a68440bb0584e81bbd
name: python-appJar
arch: src
version: 0.94.0
epoch: 0
release: 1
description: Simple tKinter GUIs in Python.
url: http://appJar.info
rpm_license: Apache 2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appJar-0.94.0-1.src.rpm
checksum_type: sha256
summary:
  An easy-to-use, feature-rich GUI wrapper for tKinter. Designed specifically for use in the classroom, but powerful enough to be used anywhere.


pkgId: af965971c9a8bea4b0139a47206b383cdf62e894f24bf1b57d630cea629418f5
name: python-app_version
arch: src
version: 1.0.1
epoch: 0
release: 1
summary: A tiny utility to get application version from pkg_resouces
url: https://github.com/lambdalisue/app_version
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-app_version-1.0.1-1.src.rpm
checksum_type: sha256
description:
  Do you write the version information on ``setup.py`` and ``__init__.py``?
  This tiny application allow you to access version information of ``setup.py`` from ``__init__.py``.
  Based on `this post <http://stackoverflow.com/questions/17583443/what-is-the-correct-way-to-share-package-version-with-setup-py-and-the-package/17638236#17638236>`_, I wrote this tiny application for convinience.
  Check `online documentation <http://python-app_version.readthedocs.org/en/latest/>`_ for more details.


pkgId: f08b4946357763fd5c6e1e825bdc09c78b391aa2e83e4f1c42930d16a1d70be1
name: python-appboot
arch: src
version: 0.2.6
epoch: 0
release: 1
summary: Use FastAPI like Django
url: https://github.com/taogeYT/appboot
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appboot-0.2.6-1.src.rpm
checksum_type: sha256
description:
  English | [中文翻译](./README.zh-CN.md)

  [AppBoot](https://github.com/taogeYT/appboot) is a FastAPI project template designed to provide a Django-like structure and development experience.

  - Python 3.9+
  - FastAPI
  - SQLAlchemy 2.0+
  - Pydantic
  - Uvicorn


  ```shell

  mkdir mysite
  cd mysite

  python3 -m venv env
  source env/bin/activate  # On Windows use `env\\Scripts\\activate`

  pip install appboot aiosqlite

  appboot startproject mysite .  # Note the trailing '.' character

  python manage.py runserver
  ```

  ```shell
  python manage.py startapp polls
  ```

  Define a `Question` model in `polls/models.py`.
  ```python
  from datetime import datetime
  from sqlalchemy.orm import Mapped
  from appboot import models

  class Question(models.Model):
      question_text: Mapped[str]
      pub_date: Mapped[datetime]
  ```

  Define a `QuestionSchema` schema in `polls/schema.py`.
  ```python
  from appboot.schema import ModelSchema
  from polls.models import Question

  class QuestionSchema(ModelSchema):
      class Meta:
          model = Question
  ```

  Write the CRUD API in `polls/views.py`.
  ```python
  from fastapi import APIRouter, Depends
  from appboot.db import create_tables
  from appboot.params import QuerySchema, QueryDepends, PaginationResult
  from polls.models import Question
  from polls.schema import QuestionSchema

  router = APIRouter(dependencies=[Depends(create_tables)])

  @router.post('/questions/', response_model=QuestionSchema)
  async def create_question(question: QuestionSchema):
      return await question.create()

  @router.get('/questions/', response_model=PaginationResult[QuestionSchema])
  async def query_questions(query: QuerySchema = QueryDepends()):
      return await query.query_result(Question.objects.clone())

  @router.get('/questions/{question_id}', response_model=QuestionSchema)
  async def get_question(question_id: int):
      return await Question.objects.get(question_id)

  @router.put('/questions/{question_id}', response_model=QuestionSchema)
  async def update_question(question_id: int, question: QuestionSchema):
      instance = await Question.objects.get(question_id)
      return await question.update(instance)

  @router.delete('/questions/{question_id}', response_model=QuestionSchema)
  async def delete_question(question_id: int):
      instance = await Question.objects.get(question_id)
      return await instance.delete()
  ```

  Wire up the API URLs in `mysite/urls.py`.
  ```python
  from fastapi import APIRouter
  from polls.views import router

  root_router = APIRouter()
  root_router.include_router(router, prefix='/polls', tags=['polls'])
  ```

  ```shell
  python manage.py runserver
  ```
  We can now access our API directly through the browser at [http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
  ![API Documentation](https://github.com/taogeYT/oss/blob/main/resource/appboot/images/polls.png?raw=true)

  Create a `QuestionQuerySchema` for complex queries in `polls/schema.py`.
  ```python
  from typing import Optional
  from appboot.params import QuerySchema
  from appboot.filters import EqField, ContainsField

  class QuestionQuerySchema(QuerySchema):
      ids: Optional[list[int]] = EqField(None, alias='pk', columns='id')  # Query questions by ID list
      question_text: Optional[str] = ContainsField(None)  # Fuzzy query question_text
  ```
  Replace `QuerySchema` with `QuestionQuerySchema` in `polls/views.py`, refresh the docs in the browser, and you will see two new query parameters in the query questions API.
  ![Complex Query Parameters](https://github.com/taogeYT/oss/blob/main/resource/appboot/images/query.png?raw=true)

  Go to [Examples](./examples) for more examples.


pkgId: 0e24f1e23b1fb3a6f78c98115afa6d3e48f1e4d4426a6412fdd8f9f6f18b517e
name: python-appcenter
arch: src
version: 5.0.0
epoch: 0
release: 1
summary: A Python wrapper around the App Center REST API.
url: https://github.com/Microsoft/appcenter-rest-python
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appcenter-5.0.0-1.src.rpm
checksum_type: sha256
description:
  This is a minimal Python wrapper around the App Center REST APIs to get you up and running. If you are looking for something more substantial, please refer to the REST API documentation: https://openapi.appcenter.ms/

  You can install with `pip install appcenter`



  ```python

  import appcenter


  client = appcenter.AppCenterClient(access_token="abc123def456")


  start = datetime.datetime.now() - datetime.timedelta(days=10)
  for group in client.crashes.get_error_groups(org_name="org", app_name="myapp", start_time=start):
      print(group.errorGroupId)


  for version in client.versions.all(org_name="org", app_name="myapp"):
      print(version)


  client.versions.upload_and_release(
      org_name="org",
      app_name="myapp",
      version="0.1",
      build_number="123",
      binary_path="/path/to/some.ipa",
      group_id="12345678-abcd-9012-efgh-345678901234",
      release_notes="These are some release notes",
      branch_name="test_branch",
      commit_hash="1234567890123456789012345678901234567890",
      commit_message="This is a commit message"
  )
  ```



  This project welcomes contributions and suggestions. Most contributions require you to agree to a
  Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
  the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

  When you submit a pull request, a CLA bot will automatically determine whether you need to provide
  a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
  provided by the bot. You will only need to do this once across all repos using our CLA.

  This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
  For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
  contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.


pkgId: ff733d3214952c35c99d0c43a7d812f1b2409c9950ea89f8960d6f78f6864d91
name: python-appcommander
arch: src
version: 0.0.35
epoch: 0
release: 1
summary: Performs controlled Android app automation through UI.
url: https://github.com/Hiveminds/appcommander
rpm_license: AGPLv3
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appcommander-0.0.35-1.src.rpm
checksum_type: sha256
description:
  [![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3106/)
  [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
  [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
  [![Code Coverage](https://codecov.io/gh/a-t-0/snn/branch/main/graph/badge.svg)](https://codecov.io/gh/a-t-0/snnalgorithms)

  Uses `uiautomator` to automatically and safely control and navigate Android
  apps.
  The user can specify some app logic (series of screens and button clicks) that
  is executed on your Android phone through ADB.



  I wanted self-host my Nextcloud calendar with 1 command, from anywhere in the
  world, no port-forwarding, no DNS stuff, no domain-name, no registrar
  configuration no nothing. That includes complete Android phone configuration
  automation for me. Some apps did not have, and perhaps may not want, a
  configuration API. Configuring Android apps with automated key-presses is not
  safe because an unexpected event may come up, e.g. a prompt for a phone update,
  a call may come in etc.

  So I wanted a safe- and controlled way to configure the app, using the UI. This
  repository verifies each step in an arbitrary script, verifies the button is
  the desired button etc. If unexpected changes are expected, the script aborts.

  Also, each phone manufacturer has a different rooting process, this repo can
  become a library to safely- and automatically root all (rootable) Android
  phones automatically (except the user must enable `ADB` themselves).



  ![image](https://github.com/HiveMinds/app-commander/blob/main/src/appcommander/org_torproject_android/V16_6_3_RC_1/flow.png?raw=true)



  First satisfy the prerequisites:

  ```bash
  pip install appcommander
  ```

  Connect your phone, and tell this code which app you want to automate, and how:

  ```bash
  python -m src.appcommander -a org.torproject.android -v "16.6.3 RC 1" -t "DAVx5"
  appcommander -a org.torproject.android -v "16.6.3 RC 1" -t DAVx5
  ```

  which is the same as:

  ```bash
  python -m src.appcommander --app-name org.torproject.android \
  --version "16.6.3 RC 1" -torify "DAVx5"
  ```

  Or, to configure DAVx5:

  ```bash
  python -m src.appcommander -a at.bitfire.davdroid -v "4.2.6" -nu \
  <your_nextcloud_username> -np <your_nextcloud_password> -o <your_onion_url>
  ```

  For more info, run:

  ```bash
  python -m src.appcommander --help
  ```



  One can simulate an android phone with:

  ```sh
  chmod +x emulate_android.sh
  ./emulate_android.sh
  ```

  And then launch the emulated android phone with:

  ```sh
  . ~/.profile
  cd ~/.android/avd/android-small.avd/
  rm *.lock
  emulator -avd android-small -netdelay none -netspeed full -skin 768x1280
  ```

  And run tests with:

  ```bash
  python -m pytest
  ```

  or to see live output, on any tests filenames containing substring: `results`:

  ```bash
  python -m pytest --capture=tee-sys

  ```



  Developers can use:

  ```bash
  conda env create --file environment.yml
  conda activate appcommander
  python -m pytest
  ```

  Currently the test coverage is `65%`. For type checking:

  ```bash
  mypy --disallow-untyped-calls --disallow-untyped-defs tests/some_test.py
  ```



  To udate the Python pip package, one can first satisfy the following requirements:

  ```bash
  pip install --upgrade pip setuptools wheel
  pip install twine
  ```

  Followed by updating the package with:

  ```bash
  rm -r dist
  rm -r build
  python3 setup.py sdist bdist_wheel
  python -m twine upload dist/\*
  ```



  ```bash
  mkdir -p ~/bin
  cp apk-ct.sh ~/bin/apk-ct
  chmod +x ~/bin/apk-ct
  ```

  Then you can rebuild and locally re-install the `appcommander` pip package the command:

  ```bash
  apk-ct
  ```



  Build the pip package with:

  ```bash
  pip install --upgrade pip setuptools wheel
  pip install twine
  ```

  Install the pip package locally with:

  ```bash
  rm -r dist
  rm -r build
  python -m build
  pip install -e .
  ```

  Upload the pip package to the world with:

  ```bash
  rm -r dist
  rm -r build
  python -m build
  python3 -m twine upload dist/\*
  ```

  that installs the latest changes into the pip package locally (into your conda
  environment).

  <!-- Un-wrapped URL's (Badges and Hyperlinks) -->



  To show how your script works, run (along with any additional input args
  required for that script):

  ```sh
  python -m src.appcommander -a <package_name> -v <app_version> -f \
  <additional arguments>
  ```

  For example:

  ```sh
  python -m src.appcommander -a "at.bitfire.davdroid" -v "4.2.6" -f  -nu \
  <some_filler> -np <some_filler> -o <some_filler>
  python -m src.appcommander -a "at.bitfire.davdroid" -v "4.2.6" -f -nu \
  asdf -np asdf -o asdf
  python -m src.appcommander -a org.torproject.android -v "16.6.3 RC 1" \
  -f -t "DAVx5"
  ```


pkgId: e91951eb53bac19efc2b8fab2c3425d95964b1946bc50a0f6bd433a31e1fb9de
name: python-appdata
arch: src
version: 2.2.1
epoch: 0
release: 1
summary: Utils to manage application data folder.
url: https://github.com/VoIlAlex/appdata
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appdata-2.2.1-1.src.rpm
checksum_type: sha256
description:
  [![Coverage Status](https://coveralls.io/repos/github/VoIlAlex/appdata/badge.svg?branch=master&)](https://coveralls.io/github/VoIlAlex/appdata?branch=master)
  [![PyPI release](https://img.shields.io/pypi/v/appdata)](https://pypi.org/project/appdata/)
  ![Build status](https://github.com/VoIlAlex/appdata/actions/workflows/publish-to-pypi.yml/badge.svg)
  [![Documentation](https://img.shields.io/readthedocs/appdata)](https://appdata.readthedocs.io/en/latest/)


  [![Maintainability](https://api.codeclimate.com/v1/badges/b909411d678ef3500d92/maintainability)](https://codeclimate.com/github/VoIlAlex/appdata/maintainability)
  [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/)
  [![License](https://img.shields.io/github/license/VoIlAlex/appdata)](https://github.com/VoIlAlex/appdata/blob/master/LICENSE.md)
  [![Downloads](https://static.pepy.tech/personalized-badge/appdata?period=total&units=international_system&left_color=grey&right_color=green&left_text=Downloads)](https://pepy.tech/project/appdata)


  [![Linux](https://svgshare.com/i/Zhy.svg)](https://svgshare.com/i/Zhy.svg)
  [![Windows](https://svgshare.com/i/ZhY.svg)](https://svgshare.com/i/ZhY.svg)
  [![macOS](https://svgshare.com/i/ZjP.svg)](https://svgshare.com/i/ZjP.svg)



  ```bash
  pip install appdata
  ```



  The detailed documentation you can find on [appdata.readthedocs.io](https://appdata.readthedocs.io/en/latest/index.html).



  To manage paths of application data folder there is `AppDataPaths` class:

  ```python
  from appdata import AppDataPaths

  app_paths = AppDataPaths('myapp')  # Name is optional. By default CWD folder name is used.
  ```

  To create the application folder tree:

  ```python
  app_paths.setup()
  ```

  There are few paths specified to manage your application data folder:

  ```python
  print(app_paths.name)  # myapp
  print(app_paths.app_data_path)  # (for Linux and MacOS) /home/<user>/.myapp
  print(app_paths.logs_path)  # (for Linux and MacOS) /home/<user>/.myapp/logs
  print(app_paths.config_path)  # (for Linux and MacOS) /home/<user>/.myapp/default.ini
  print(app_paths.log_file_path)  # (for Linux and MacOS) /home/<user>/.myapp/logs/myapp.log
  ```

  Every path could be customized. See options [here](https://appdata.readthedocs.io/).



  See [CONTRIBUTING.md](CONTRIBUTING.md)



  [MIT](LICENSE.md)


pkgId: c75f4f036dbd2071959ea5940499eabfe94a863c45676b9e265070af8456347d
name: python-appdirs
arch: src
version: 1.4.4
epoch: 0
release: 1
summary: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir".
url: http://github.com/ActiveState/appdirs
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appdirs-1.4.4-1.src.rpm
checksum_type: sha256
description:
  What directory should your app use for storing user data? If running on Mac OS X, you
  should use::
      ~/Library/Application Support/<AppName>
  If on Windows (at least English Win XP) that should be::
      C:\Documents and Settings\<User>\Application Data\Local Settings\<AppAuthor>\<AppName>
  or possibly::
      C:\Documents and Settings\<User>\Application Data\<AppAuthor>\<AppName>
  for `roaming profiles <http://bit.ly/9yl3b6>`_ but that is another story.
  On Linux (and other Unices) the dir, according to the `XDG
  spec <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_, is::
      ~/.local/share/<AppName>


pkgId: 0bd820ae17030360ee1b17a22435024d5ce5a00f3842e399e39f01489498d1b1
name: python-appdynamics
arch: src
version: 24.7.0.6967
epoch: 0
release: 1
summary: Python Agent for AppDynamics
url: https://www.appdynamics.com/python
rpm_license: See license in package description
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appdynamics-24.7.0.6967-1.src.rpm
checksum_type: sha256
description:
   APPDYNAMICS, INC.
   END USER LICENSE AGREEMENT
   1. SOFTWARE LICENSE.
   1.1 LICENSE GRANT.
   This End User License Agreement (this “Agreement”) between AppDynamics, Inc., a Delaware corporation with its principal place of business located at 303 Second Street, North Tower, 8th Floor, San Francisco, CA 94107 (“AppDynamics”) and the customer set forth in the Order Form (as defined below) (“End User”) is effective as of the date the initial Order Form is executed or the date this Agreement is referenced by the applicable purchase order (the “Effective Date”). This Agreement shall govern each order form, sales quotation, proposal, purchase order or other ordering document that references this Agreement (each an “Order Form”). Each Order Form will form part of this Agreement. Subject to the terms and conditions of this Agreement, AppDynamics hereby grants to End User, during the License Term (as defined below), a non-exclusive, non-transferable, non-sublicensable right and license to use the software-as-a-service and/or on-premises version of AppDynamics’ application intelligence software product, including one Controller, as designated in the Order Forms (the “Software”) for internal business purposes only for the quantity of units in the Order Form. AppDynamics Test & Dev Edition licenses shall only be used in test-only non-production environments. For purposes hereof, the “License Term” begins on the date AppDynamics delivers the Software license keys to End User and extends for the period specified in the applicable Order Form (unless earlier terminated in accordance with this Agreement). For the avoidance of doubt, End User’s affiliates (and employees thereof) shall not use the Software without AppDynamics’ prior written consent. Notwithstanding anything to the contrary herein, this Agreement does not supersede a signed agreement between AppDynamics and End User with respect to the Software.
   1.2 RESTRICTIONS ON USE.
   Except as otherwise expressly provided in this Agreement, End User shall not (and shall not permit any third party to): (a) sublicense, sell, resell, transfer, assign, distribute, share, lease, rent, make any external commercial use of, outsource, use on a timeshare or service bureau, or use in an application service provider or managed service provider environment, or otherwise generate income from the Software; (b) copy the Software onto any public or distributed network, except for an internal and secure cloud computing environment; (c) cause the decompiling, disassembly, or reverse engineering of any portion of the Software, or attempt to discover any source code or other operational mechanisms of the Software (except where such restriction is expressly prohibited by law without the possibility of waiver, and then only upon prior written notice to AppDynamics); (d) modify, adapt, translate or create derivative works based on all or any part of the Software; (e) use any Third Party Software (as defined below) provided with the Software other than with the Software; (f) modify any proprietary rights notices that appear in the Software or components thereof; (g) publish the results of any benchmarking tests run on any Third Party Software; (h) use any Software in violation of any applicable laws and regulations (including any export laws, restrictions, national security controls and regulations) or outside of the license scope set forth in Section 1.1; (i) use the Software in support of any nuclear proliferation, chemical weapon, biological weapon or missile proliferation activity; (j) configure the Software to collect any (1) social security numbers or other government-issued identification numbers, (2) passwords or other authentication credentials, (3) health information, biometric data, genetic data, or payment/financial information, (4) any data relating to a person under the age of 13 years old, or (5) any other data that is subject to regulatory or contractual handling requirements (e.g., PCI, HIPAA, or state and federal data security laws) (collectively, “Prohibited Data”); or (k) use the Software to (1) store, download or transmit infringing, libelous, or otherwise unlawful or tortious material, or malicious code or malware, or (2) engage in phishing, spamming, denial-of-service attacks or other fraudulent or criminal activity, (3) interfere with or disrupt the integrity or performance of third party systems, or the Software or data contained therein, or (4) attempt to gain unauthorized access to the Software or AppDynamics' systems or networks, or (5) perform, or engage any third party to perform, authenticated or unauthenticated penetration testing, vulnerability assessments or other security assessments on the SaaS version of the Software. End User shall not export or re-export, directly or indirectly, any Software or technical data or any copy, portions or direct product thereof (i) in violation of any applicable laws and regulations, (ii) to any country for which the United States or any other government, or any agency thereof, at the time of export requires an export license or other governmental approval, including Cuba, Libya, North Korea, Iran, Iraq, or Rwanda or any other Group D:1 or E:2 country (or to a national or resident thereof) specified in the then current Supplement No. 1 to part 740 of the U.S. Export Administration Regulations (or any successor supplement or regulations, without first obtaining such license or approval) or (ii) to anyone on the U.S. Treasury Department’s list of Specially Designated Nationals or the U.S. Commerce Department’s Table of Denial Orders. End User shall, at its own expense, obtain all necessary customs, import, or other governmental authorizations and approvals.
   1.3 EVALUATION USE.
   If End User accessed the Software pursuant to a no-fee or beta evaluation (“Evaluation Use”), then the License Term is for the period enabled by the license key for the Software provided by AppDynamics. AppDynamics shall have the right to downgrade, limit or otherwise modify the Software provided for Evaluation Use at any time without notice, and no warranty, indemnity, Maintenance or Support obligations of AppDynamics will apply to Evaluation Use. End User may use the number and type of licenses indicated by AppDynamics in writing prior to End User downloading or accessing the Software, which will be enabled by End User’s specific license key. AppDynamics has the right to immediately revoke and terminate any Evaluation Use at any time. End User agrees to provide feedback related to the Software as reasonably requested by AppDynamics. End User grants to AppDynamics, without charge, the fully paid-up, perpetual, sublicensable right to exploit such feedback for any purpose. Evaluation Use is not a guarantee of future product features and should not be relied upon in making any purchasing decisions.
   1.4 UNAUTHORIZED USE.
   End User shall notify AppDynamics promptly of any unauthorized use of any password or account or any other known or suspected breach of security or misuse of the Software. End User is responsible for use of the Software by any and all employees, contractors, or other users that it allows to access the Software.
   1.5 MAINTENANCE AND SUPPORT.
   “Support” is defined as AppDynamics' obligations to respond to support requests as described in Exhibit A (Enterprise Support). “Maintenance” means AppDynamics’ obligations related to error resolution, bug fixes and the provision of updates and upgrades made generally commercially available by AppDynamics in its sole discretion, all as described in Exhibit A. Subject to End User’s payment of the fees set forth in the applicable Order Form(s), AppDynamics will (a) provide Maintenance and Support for the Software, and (b) solely if End User has purchased access to the software-as-a-service version of the Software, make the Software available to End User in accordance with Exhibit B (Availability and Security). For time-limited licenses for the Software (as set forth in an Order Form, “Subscription Licenses”), the fees for Maintenance and Support are included in the fees for the Software. For perpetual licenses, (i) AppDynamics shall invoice End User the applicable fees for Maintenance and Support (“Maintenance and Support Fees”) upon execution of the Order Form, and (ii) subject to payment of the Maintenance and Support Fees, AppDynamics will provide Support for the Maintenance and Support period set forth in the Order Form, as it may be renewed (the “Maintenance and Support Term”). If Maintenance and Support terminates with respect to any perpetual licenses, and End User is in good standing under this Agreement, then End User may reinstate Maintenance and Support on payment of the cumulative Maintenance and Support Fees applicable for the period during which Maintenance and Support lapsed, plus Maintenance and Support Fees for the reinstated Maintenance and Support Term. Notwithstanding anything herein to the contrary, if End User receives Support from an authorized partner of AppDynamics (“Partner”), then the support terms agreed upon by End User and such Partner shall govern in lieu of those set forth in Exhibit A, and AppDynamics shall have no support obligations to End User.
   1.6 SERVICES.
   Subject to the terms hereof (including payment of any applicable fees set forth in the Order Form), AppDynamics will provide the training, enablement and/or other services described in an Order Form (or statement of work referencing this Agreement), if any (“Services”). All Services will be rendered on a time and materials basis. AppDynamics will not exceed the total time purchased without prior written approval from End User. If the Services purchased have been consumed, AppDynamics will stop the Services until additional Services have been purchased. If End User elects not to purchase additional Services, then the Services will be deemed complete. If not used, pre-purchased Services and expenses expire twelve (12) months after the date purchased (unless otherwise set forth in the applicable Order Form). End User agrees to provide reasonable cooperation and information as necessary to permit AppDynamics to perform the Services. End User will reimburse AppDynamics for travel and expenses (at cost) incurred in connection with the Services (if any) by the Invoice Due Date (as defined below). Services will be performed on business days (a business day means Monday through Friday, excluding national holidays, during working hours, in the location where the Services are delivered). Saturday Services, and hours worked during the week that exceed the local working hours in a week, will be charged at the agreed rate x 1.5. Services performed on Sundays or national holidays will be charged at 2 x the agreed rate. If End User cancels or delays any scheduled Services less than ten (10) business days before the start date of such Services, then AppDynamics will deduct from End User's account (or End User will pay for) the amount of Services that were scheduled in any of the ten (10) business days following the date of cancellation (or notification of the delay, as applicable), and End User will fully reimburse AppDynamics for any reasonable travel and expenses incurred by AppDynamics for such Services (and for any Services rescheduled by End User) for which AppDynamics is unable to obtain a refund. During the License Term (for Subscription Licenses) or during the Maintenance and Support Term (for perpetual licenses), and in each case for a period of twelve (12) months thereafter, without AppDynamics’ prior written approval, End User will not solicit for employment or consultancy any AppDynamics’ employees who participated in the performance of Services.
   2. FEES.
   2.1 PRICING.
   End User will be invoiced for those amounts and at those prices set forth in an Order Form (an “Invoice”), provided, however, that, for Evaluation Use, End User shall not be billed unless otherwise specified by AppDynamics in writing prior to End User downloading or accessing (as applicable) the Software. Fees do not include any customization of the Software (nor support for any such customizations, unless otherwise agreed in writing). If End User’s usage of the Software is in excess of those amounts set forth in the Order Form, End User will be billed for those overages at a pro-rated amount for the remainder of the applicable License Term, based on AppDynamics’ then-current standard pricing. If AppDynamics believes in good faith that End User’s usage of the Software exceeds that set forth on the applicable Order Form, End User agrees to allow AppDynamics to audit End User’s use of the Software (not more frequently than twice per calendar year), upon at least twenty-four (24) hours’ notice, to determine the actual Software use, using a commercially reasonable auditing procedure. End User acknowledges that purchases made under this Agreement are neither contingent on the delivery of any future functionality or features nor dependent on any oral or written public comments made by AppDynamics regarding future functionality or features.
   2.2 PAYMENTS.
   End User shall pay Invoices within thirty (30) days of the invoice date (the “Invoice Due Date”). If End User reasonably disputes any Invoice, (i) End User shall provide AppDynamics with written notice of such dispute, including the grounds therefor (a “Dispute Notice”), prior to the Invoice Due Date, (ii) End User and AppDynamics shall, for a period of fifteen (15) days following AppDynamics’ receipt of such Dispute Notice, negotiate in good faith to resolve the dispute and (iii) if such dispute remains unresolved at the end of such period, the parties shall retain all of their respective rights under this Agreement (including, without limitation, any action for non-payment of the fees set forth herein). All payment obligations are non-cancelable and all amounts paid are non-refundable, except (a) for amounts paid in error that are not actually due under this Agreement, and (b) as set forth in Sections 6.1 and 7.1. The fees paid by End User are exclusive of all taxes, levies, or duties imposed by taxing authorities, if any, and End User shall be responsible for payment of all such taxes, levies, or duties, excluding taxes based on AppDynamics’ income. End User represents and warrants that the billing and contact information provided to AppDynamics is complete and accurate, and AppDynamics shall have no responsibility for any Invoices that are not received due to inaccurate or missing information provided by End User. End User shall pay interest on all payments not received by the Invoice Due Date at a rate of one percent (1%) per month or the maximum amount allowed by law, whichever is lesser. All amounts due under this Agreement shall be paid by End User in full without any set-off, counterclaim, deduction or withholding (other than any deduction or withholding of tax as required by law). If requested by AppDynamics, End User will obtain and furnish to AppDynamics tax receipts or other certificates issued by the competent taxation office showing the payments of the withholding tax within a reasonable time after payment. Following written notice, AppDynamics shall be entitled to suspend End User’s access to the Software if payments are not received within thirty (30) days of the Invoice Due Date. Notwithstanding anything herein to the contrary, if End User makes its payments pursuant to this Agreement to a Partner, then the payment terms agreed by End User and such Partner shall govern to the extent anything in this Section 2 conflicts with such Partner payment terms.
   3. CONFIDENTIALITY
   3.1 SCOPE AND RESTRICTIONS.
   “Confidential Information” means all information of a party (“Disclosing Party”) disclosed to the other party (“Receiving Party”) that is designated in writing or identified as confidential at the time of disclosure or should be reasonably known by the Receiving Party to be confidential due to the nature of the information disclosed and the circumstances surrounding the disclosure. The terms of this Agreement, the Software, any technical or other documentation relating to the Software, logins, passwords and other access codes and any and all information regarding AppDynamics’ business, products and services are the Confidential Information of AppDynamics. The Receiving Party will: (i) not use the Disclosing Party’s Confidential Information for any purpose outside of this Agreement; (ii) not disclose such Confidential Information to any person or entity, other than its affiliates, employees, consultants, agents and professional advisers who have a “need to know” for the Receiving Party to exercise its rights or perform its obligations hereunder, provided that such employees, consultants, and agents are bound by agreements or, in the case of professional advisers, ethical duties respecting such Confidential Information in accordance with the terms of this Section 3; and (iii) use reasonable measures to protect the confidentiality of such Confidential Information. If the Receiving Party is required by applicable law or court order to make any disclosure of such Confidential Information, it will first give written notice of such requirement to the Disclosing Party, and, to the extent within its control, permit the Disclosing Party to intervene in any relevant proceedings to protect its interests in its Confidential Information, and provide full cooperation to the Disclosing Party in seeking to obtain such protection. Further, this Section 3 will not apply to information that the Receiving Party can document: (i) was rightfully in its possession or known to it prior to receipt without any restriction on its disclosure; (ii) is or has become public knowledge or publicly available through no fault of the Receiving Party; (iii) is rightfully obtained by the Receiving Party from a third party without breach of any confidentiality obligation; or (iv) is independently developed by employees of the Receiving Party who had no access to such information.
   3.2 EQUITABLE RELIEF.
   The Receiving Party acknowledges that unauthorized disclosure of the Disclosing Party’s Confidential Information could cause substantial harm to the Disclosing Party for which damages alone might not be a sufficient remedy and, therefore, that upon any such disclosure by the Receiving Party the Disclosing Party will be entitled to seek appropriate equitable relief in addition to whatever other remedies it might have at law or equity.
   4. PROPRIETARY RIGHTS. AppDynamics and its suppliers own and shall retain all proprietary rights, including all copyright, patent, trade secret, trademark and all other intellectual property rights, in and to the Software and the results of any Services. End User acknowledges that the rights granted under this Agreement do not provide End User with title to or ownership of the Software. Certain “free” or “open source” based software (the “FOSS Software”) and third party software included with the Software (the “Third Party Software”) is shipped with the Software but is not considered part of the Software hereunder. A list of the FOSS Software and Third Party Software is set forth on the webpage located at https://docs.appdynamics.com/display/DASH/Legal+Notices With respect to Third Party Software included with the Software, such Third Party Software suppliers are third party beneficiaries of this Agreement. End User’s use of such FOSS Software is subject to the terms of the licenses set forth on such webpage. The Software and Third Party Software may only be used by End User as prescribed by the AppDynamics documentation located at http://docs.appdynamics.com (as it may be updated from time to time, the “Documentation”).
   5. TERM AND TERMINATION.
   The term of this Agreement begins on the Effective Date and will remain in effect until all Subscription Licenses (and Maintenance and Support Terms, if applicable) expire or until this Agreement is otherwise terminated in accordance with the terms hereof, whichever occurs first (the “Term”). This Agreement may be renewed at any time by execution of an Order Form referencing this Agreement, and any such renewal will be deemed part of the “Term” hereunder. If either party commits a material breach of this Agreement, and such breach has not been cured within thirty (30) days after receipt of written notice thereof, the non-breaching party may terminate this Agreement, except that AppDynamics may immediately terminate this Agreement and/or End User’s license to the Software upon End User’s breach of Section 1.2. Either party may also terminate this Agreement upon written notice if the other party suspends payment of its debts or experiences any other insolvency or bankruptcy-type event. Upon expiration or termination of this Agreement for any reason, (i) with respect to Subscription Licenses, all rights granted to End User shall terminate and End User shall destroy any copies of the Software and Documentation within End User’s possession and control; (ii) with respect to perpetual licenses, these will survive termination of this Agreement unless AppDynamics terminates the Agreement for End User’s breach of this Agreement in which case all rights granted to End User shall terminate and End User shall destroy any copies of the Software and Documentation within End User’s possession and control; and (iii) each Receiving Party will return or destroy, at the Disclosing Party’s option, the Disclosing Party’s Confidential Information in the Receiving Party’s possession or control. All fees that have accrued as of such expiration or termination, and Sections 1.2, 1.3, 1.4, 2, 3, 4, 5, 6.2 and 7 through 12, will survive any expiration or termination hereof.
   6. WARRANTIES.
   6.1 LIMITED WARRANTY.The limits below will not apply to the extent prohibited by applicable law.
   AppDynamics warrants that during the first thirty (30) days following the date the Software is purchased, the Software will, in all material respects, conform to the functionality described in the then-current Documentation for the applicable Software version. AppDynamics’ sole and exclusive obligation, and End User’s sole and exclusive remedy, for a breach of this warranty shall be that AppDynamics shall be required to use commercially reasonable efforts to repair or replace the Software to conform in all material respects to the Documentation, and if AppDynamics is unable to materially restore such functionality within thirty (30) days from the date of written notice of such breach, End User shall be entitled to terminate the license to the affected Software upon written notice and AppDynamics shall promptly provide a pro-rata refund of the Software license fees (or Maintenance and Support Fees, for perpetual licenses) that have been paid in advance for the remainder of the License Term for the applicable Software (beginning on the date of termination). End User must notify AppDynamics in writing of any warranty breaches within such warranty period, and End User must have installed and configured the Software in accordance with the Documentation to be eligible for the foregoing remedy.
   6.2 WARRANTY DISCLAIMER.
   EXCEPT AS EXPRESSLY PROVIDED IN THIS SECTION 6, ALL SOFTWARE, DOCUMENTATION, MAINTENANCE AND SUPPORT AND SERVICES ARE PROVIDED “AS IS” AND APPDYNAMICS AND ITS SUPPLIERS EXPRESSLY DISCLAIM ANY AND ALL OTHER REPRESENTATIONS AND WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT THERETO, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR THE CONTINUOUS, UNINTERRUPTED, ERROR-FREE, VIRUS-FREE, OR SECURE ACCESS TO OR OPERATION OF THE SOFTWARE. APPDYNAMICS EXPRESSLY DISCLAIMS ANY WARRANTY AS TO THE ACCURACY OR COMPLETENESS OF ANY INFORMATION OR DATA ACCESSED OR USED IN CONNECTION WITH THE SOFTWARE, DOCUMENTATION, MAINTENANCE AND SUPPORT, OR SERVICES. AppDynamics is not responsible for any delays, delivery failures, or any other loss or damage resulting from the transfer of data over communications networks and facilities, including the Internet, and End User acknowledges that the Software and Documentation may be subject to limitations, delays and other problems inherent in the use of such communications facilities. The Software is not fault-tolerant and is not designed or intended for use in hazardous environments, including without limitation, in the operation of aircraft or other modes of human mass transportation, nuclear or chemical facilities, life support systems, implantable medical equipment, motor vehicles or weaponry systems, or any other application in which failure of the Software could lead to death or serious bodily injury of a person, or to severe physical or environmental damage (each, a “High Risk Use”). AppDynamics expressly disclaims any express or implied warranty or representation of fitness for High Risk Use. AppDynamics shall not be liable to End User for any loss, damage or harm suffered by End User that is directly or indirectly caused by End User’s unauthorized use of the Software to process Prohibited Data.
   7. INDEMNIFICATION.
   7.1 BY APPDYNAMICS.
   AppDynamics agrees at its expense to defend End User against (or, at AppDynamics’ option, settle), any third party claim to the extent such claim alleges that the Software infringes or misappropriates any patent, copyright, trademark or trade secret of a third party, and AppDynamics shall pay all costs and damages finally awarded against End User by a court of competent jurisdiction as a result of any such claim. In the event that the use of the Software is, or in AppDynamics’ sole opinion is likely to become, subject to such a claim, AppDynamics, at its option and expense, may (a) replace the applicable Software with functionally equivalent non-infringing technology, (b) obtain a license for End User’s continued use of the applicable Software, or (c) terminate the license and provide a pro-rata refund of the Software license fees (or Maintenance and Support Fees, for perpetual licenses) that have been paid in advance for the remainder of the License Term for the applicable Software (beginning on the date of termination). The foregoing indemnification obligation of AppDynamics will not apply: (1) if the Software is modified by End User or its agent; (2) if the Software is combined with other non-AppDynamics products, applications, or processes, but solely to the extent the alleged infringement is caused by such combination; or (3) to any unauthorized use of the Software. The foregoing shall be End User’s sole remedy with respect to any claim of infringement of third party intellectual property rights.
   7.2 BY END USER.
   End User agrees to defend, at its expense, AppDynamics and its affiliates, its suppliers and its resellers against any third party claim to the extent such claim arises from or is made in connection with End User’s breach of Section 1 or End User’s negligence or willful misconduct, and End User shall pay all costs and damages finally awarded against AppDynamics by a court of competent jurisdiction as a result of any such claim.
   7.3 INDEMNIFICATION REQUIREMENTS.
   In connection with any claim for indemnification under this Section 7, the indemnified party must promptly provide the indemnifying party with notice of any claim that the indemnified party believes is within the scope of the obligation to indemnify, provided, however, that the failure to provide such notice shall not relieve the indemnifying party of its obligations under this Section 7, except to the extent that such failure materially prejudices the indemnifying party’s defense of such claim. The indemnified party may, at its own expense, assist in the defense if it so chooses, but the indemnifying party shall control the defense and all negotiations related to the settlement of any such claim. Any such settlement intended to bind either party shall not be final without the other party’s written consent, which consent shall not be unreasonably withheld, conditioned or delayed; provided, however, that End User’s consent shall not be required when AppDynamics is the indemnifying party if the settlement involves only the payment of money by AppDynamics.
   8. LIMITATION OF LIABILITY. The limits below will not apply to the extent prohibited by applicable law.
   8.1 EXCEPT FOR LIABILITY ARISING OUT OF END USER’S BREACH OF SECTION 1.2 (RESTRICTIONS ON USE), IN NO EVENT SHALL EITHER PARTY BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE OR CONSEQUENTIAL DAMAGES, INCLUDING DAMAGES FOR LOSS OF REVENUES OR PROFITS, LOSS OF USE, BUSINESS INTERRUPTION, OR LOSS OF DATA, WHETHER IN AN ACTION IN CONTRACT OR TORT, EVEN IF THE OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
   8.2 EXCEPT FOR LIABILITY ARISING OUT OF END USER’S BREACH OF SECTION 1.2 (RESTRICTIONS ON USE), NEITHER PARTY’S LIABILITY FOR ANY DAMAGES (WHETHER FOR BREACH OF CONTRACT, MISREPRESENTATIONS, NEGLIGENCE, STRICT LIABILITY, OTHER TORTS OR OTHERWISE) UNDER THIS AGREEMENT SHALL EXCEED AN AMOUNT EQUAL TO THE TOTAL FEES PAID (PLUS FEES PAYABLE) TO APPDYNAMICS DURING THE TWELVE (12) MONTHS IMMEDIATELY PRECEDING THE CLAIM GIVING RISE TO SUCH DAMAGES. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY.
   9. FORCE MAJEURE.
   Except for payment obligations, neither party hereto will be liable for defaults or delays due to acts of God, or the public enemy, acts or demands of any government or governmental agency, fires, earthquakes, floods, accidents, or other unforeseeable causes beyond its reasonable control and not due to its fault or negligence.
   10. DATA COLLECTION.
   End User acknowledges that AppDynamics’ application server and machine software agents (the “Agents”) collect metrics that relate to the performance, health and resource of an application, its components (transactions, code libraries) and related infrastructure (nodes, tiers) that service those components. In addition, AppDynamics collects metrics on End Users’ activities, such as web pages visited, length of visit, and which features of the Software an End User uses. If End User provides AppDynamics with any personally identifiable information (“personal data”), End User represents and warrants that such information has been collected by End User in accordance with the provisions of all applicable data protection legislation and that End User has all right and consents necessary to provide such personal data to AppDynamics. End User will indemnify AppDynamics for reasonable costs and other amounts that AppDynamics may incur relating to any breach of this Section.
   11. US GOVERNMENT MATTERS.
   As defined in FAR section 2.101, the Software and Documentation are “commercial items” and according to DFAR section 252.227 7014(a)(1) and (5) are deemed to be “commercial computer software” and “commercial computer software documentation.” Consistent with DFAR section 227.7202 and FAR section 12.212, any use modification, reproduction, release, performance, display, or disclosure of such commercial software or commercial software documentation by the U.S. Government will be governed solely by the terms of this Agreement and will be prohibited except to the extent expressly permitted by the terms of this Agreement.
   12. MISCELLANEOUS.
   This Agreement shall be governed by and construed under the laws of the State of California, U.S.A. The parties consent to the exclusive jurisdiction and venue of the courts located in and serving San Francisco, California. Failure by either Party to exercise any of its rights under, or to enforce any provision of, this Agreement will not be deemed a waiver or forfeiture of such rights or ability to enforce such provision. If any provision of this Agreement is held by a court of competent jurisdiction to be illegal, invalid or unenforceable, such provision will be amended to achieve as nearly as possible the same economic effect of the original provision and the remainder of this Agreement will remain in full force and effect. This Agreement and its exhibits, together with the Order Form(s) and any statements of work incorporating this Agreement, if applicable, represent the entire agreement between the parties and supersede any previous or contemporaneous oral or written agreements or communications regarding the subject matter of this Agreement. The person signing or otherwise accepting this Agreement for End User represents that s/he is duly authorized by all necessary and appropriate corporate action to enter this Agreement on behalf of End User. Any modification to this Agreement must be in writing and signed by a duly authorized agent of both parties. The Uniform Computer Information Transactions Act (UCITA) does not apply to this Agreement. This Agreement shall control over additional or different terms of any purchase order, confirmation, invoice, statement of work or similar document (other than an AppDynamics Order Form, which will take precedence), even if accepted in writing by both parties, and waivers and amendments to this Agreement shall be effective only if made by non-pre-printed agreements clearly understood by both parties to be an amendment or waiver to this Agreement. For purposes of this Agreement, “including” means “including without limitation.” The rights and remedies of the parties hereunder will be deemed cumulative and not exclusive of any other right or remedy conferred by this Agreement or by law or equity. No joint venture, partnership, employment, or agency relationship exists between the parties as a result of this Agreement or use of the Software. AppDynamics reserves the right to perform its obligations from locations and/or through use of affiliates and subcontractors, worldwide, provided that AppDynamics will be responsible for such parties. End User may not assign this Agreement without the prior written approval of AppDynamics and any purported assignment in violation of this section shall be void; AppDynamics may assign, transfer or subcontract this Agreement in whole or in part. Upon any assignment of this Agreement by End User that is approved by AppDynamics, any licenses that contain an “unlimited” aspect will, with respect to End User or the successor entity, as applicable, be capped at the number of authorized Software units in use immediately prior to such assignment. End User agrees that AppDynamics may refer to End User by its trade name and logo, and may briefly describe End User’s business, in AppDynamics’ marketing materials and website. AppDynamics may give notice to End User by electronic mail to End User’s email address on record in End User’s account information, or by written communication sent by first class mail or pre-paid post to End User’s address on record in End User’s account information. End User may give notice to AppDynamics at any time by any letter delivered by nationally recognized overnight delivery service or first class postage prepaid mail to AppDynamics at the following address or such other address as may be notified to End User from time to time: AppDynamics, Inc., 303 Second Street, North Tower, 8th Floor, San Francisco, CA 94107, Attn: Legal Department. Notice under this Agreement shall be deemed given when received, if personally delivered; when receipt is electronically confirmed, if transmitted by email; the day after it is sent, if sent for next day delivery by recognized overnight delivery service; and upon receipt, if sent by certified or registered mail, return receipt requested.
   EXHIBIT A
   ENTERPRISE SUPPORT
   GENERAL REQUIREMENTS.
   AppDynamics will provide access to a ticketing system and email address (help@appdynamics.com), which will be available twenty-four (24) hours per day, seven (7) days per week. The email account will be maintained by qualified support specialists, who shall use commercially reasonable efforts to answer questions and resolve problems regarding the Software.
   HOURS OF OPERATION.
   Support is available twenty-four (24) hours per day, seven (7) days per week.
   ERROR CLASSIFICATION.
   Any reported errors are classified in the following manner, in each case when caused by
   the Software:


pkgId: fc0b08b4c4373ee67d430b0fc1014f27324ee3d72c43bbd2173b48eac3cfbbc7
name: python-appeal
arch: src
version: 0.6.3
epoch: 0
release: 1
summary: A powerful & Pythonic command-line parsing library. Give your program Appeal!
url: https://pypi.org/project/appeal/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appeal-0.6.3-1.src.rpm
checksum_type: sha256
description:
  ![## Appeal](https://raw.githubusercontent.com/larryhastings/appeal/master/resources/images/appeal.logo.png)

  ![## Give your program Appeal!](https://raw.githubusercontent.com/larryhastings/appeal/master/resources/images/give.your.program.appeal.png)



  [![# test badge](https://img.shields.io/github/actions/workflow/status/larryhastings/appeal/test.yml?branch=master&label=test)](https://github.com/larryhastings/appeal/actions/workflows/test.yml) [![# python versions badge](https://img.shields.io/pypi/pyversions/appeal.svg?logo=python&logoColor=FBE072)](https://pypi.org/project/appeal/)




  ```Python
  import appeal
  import sys

  app = appeal.Appeal()

  @app.command()
  def hello(name):
      print(f"Hello, {name}!")

  app.main()
  ```

  Here's a simple ``fgrep`` utility:

  ```Python
  import appeal
  import sys

  app = appeal.Appeal()

  @app.command()
  def fgrep(pattern, *files, ignore_case=False):
      if not files:
          files = ['-']
      print_file = len(files) > 1
      if ignore_case:
          pattern = pattern.lower()
      for file in files:
          if file == "-":
              f = sys.stdin
          else:
              f = open(file, "rt")
          for line in f:
              if ignore_case:
                  match = pattern in line.lower()
              else:
                  match = pattern in line
              if match:
                  if print_file:
                      print(file + ": ", end="")
                  print(line.rstrip())
          if file != "-":
              f.close()


  if __name__ == "__main__":
      app.main()
  ```




  Appeal is a command-line argument processing library for
  Python, like `argparse`, `optparse`, `getopt`,
  `docopt`, `Typer`, and `click`.  But Appeal takes a
  refreshing new approach.

  Other libraries have complicated, cumbersome interfaces
  that force you to repeat yourself over and over.
  Appeal leverages Python's rich function call interface,
  making it effortless to define your command-line interface.
  You write Python functions, and Appeal translates them into
  command-line options and arguments.

  Appeal provides amazing power and flexibility--but it's
  also intuitive, because it mirrors Python itself.
  If you understand how to write Python functions,
  you're already halfway to understanding Appeal!

  Appeal has only one dependency,
  [my **big** library.](https://github.com/larryhastings/big)

  Appeal is currently only supported for POSIX platforms
  (UNIX, Linux, BSD, OS X, etc).  It might work on Windows
  but this has not yet been tested.



  Appeal isn't like other command-line parsing libraries.
  In fact, you really shouldn't think of Appeal as a
  "command-line parsing library" per se. And, although you
  work with Appeal by passing in functions for Appeal to call,
  you shouldn't think of these functions as "callbacks".

  Appeal lets you design *APIs* callable from the command-line.
  It's just like any other Python library API--except that
  the caller calls you from the command-line instead of from
  Python.  Appeal is the mechanism converting between these two
  domains: it translates your API into command-line semantics,
  then translates the user's command-line back into calls to your API.

  This raises another good point: the API you build using Appeal
  also often makes for a very nice *automation API,* allowing
  your program to also be used as a library by other programs
  with minimal effort.






  Let's start by establishing the terminology we'll use
  for command-lines, based on command-line idioms established
  by POSIX and by popular programs.  Here's a sample
  command-line, illustrating all the various types of things
  you might ever see:

      % ./script.py --debug add --flag ro -v -xz myfile.txt
        ^           ^       ^   ^      ^  ^  ^   ^
        |           |       |   |      |  |  |   |
        |           |       |   |      |  |  |   argument
        |           |       |   |      |  |  |
        |           |       |   |      |  |  multiple short options
        |           |       |   |      |  |
        |           |       |   |      |  short option
        |           |       |   |      |
        |           |       |   |      oparg
        |           |       |   |
        |           |       |   long option
        |           |       |
        |           |       command
        |           |
        |           global long option
        |
        program name

  Command-lines are a sequence of strings separated by
  whitespace.  The meaning of each string can depend
  both on the position of the string and the characters
  in the string itself.

  An *argument* is any whitespace-delimited string on the
  command-line that doesn't start with a `-` (minus sign).
  Unless it's an *oparg*--which we'll talk about in a minute--the
  meaning of an argument is defined by its position.  For example,
  if you ran:

      fgrep WM_CREATE window.c

  `WM_CREATE` and `window.c`
  would be *arguments;* the first argument, `WM_CREATE`,
  would be the string you wanted to search for, and `window.c`
  would be the name of the file you wanted to search.

  A *command* is a special kind of argument some programs
  use to specify what function you want the program to perform.
  A good example of a program that uses commands is `git`;
  when you run `git add` or `git commit`, `add` and `commit`
  are both *commands.*  The command is always the first
  argument to a program that uses them.

  If a string on the command-line starts with a `-` (minus
  sign), that's an *option*.  There are two styles of
  option: *short options* and *long options.*

  *Short options* start with a single dash, `-`.  This is
  followed by one or more individual characters, which
  are the short option strings.  In the above example,
  we specify two sets of short options: the first is `-v`,
  the second is `-xz`.  You can combine options togther,
  and it's the same as specifying them separately.  We
  could have said `-vxz`, or `-v -x -z`; these both do
  the same thing.  When we talk about short options, we
  say the word "dash" followed by the letter.  For example,
  `-v` would be pronounced "dash v".

  *Long options* start with two dashes, `--`.  Everything
  after the two dashes is the name of the option.  In the
  above example, we can see one long option, `--flag`.
  Again, when we talk about long options, we say the
  dashes out loud, followed by the words from the option.
  For example, `--flag` would be pronounced "dash dash flag".

  Both types of options can optionally take one (or more)
  arguments of their own.  An argument to an option is
  called an *oparg.*  In the above example, the long option
  `--flag` takes the oparg `ro`.

  Finally, there are *global options* and *command
  options.*  Global options apply to the entire
  program, are always available, and are specified
  *before* the command.  Command options are
  command-specific, and appear *after* the command.
  Global options can be long options or short options;
  command options can be long options or short options, too.




  Now let's consider a Python function call:

  ```Python



  def fgrep(pattern, filename, *, ignore_case=False):
      ...
  ```

  We can draw some similarities between Python
  function calls and command-lines.

  For example, they both support arguments where
  position is significant.  A command-line *argument*
  is similar to a Python function *positional*
  parameter, in that they're both identified by
  position.

  Python function calls and command-lines also
  both support arguments identified by name.
  A command-line *option* is similar to a Python
  *keyword-only* parameter.

  > In Python, all parameters after `*args` or `*` are
  > keyword-only parameters.  You can only pass in arguments
  > to those parameters by name.  If you called `fgrep`
  > yourself, you could only pass in a value for `ignore_case`
  > like this:
  >
  >     result = fgrep(p, f, ignore_case=True)

  This leads us to the fundamental concept behind Appeal.
  With Appeal, you write a Python function, and tell
  Appeal that it represents a *command.*  Appeal
  examines the function, translating its parameters into
  command-line features.  Positional parameters become
  command-line arguments, and keyword-only parameters
  become options.

  (Technically, Appeal translates both *positional parameters*
  and *positional-or-keyword parameters* into arguments.
  For the sake of clarity and consiseness, I'll always
  refer to these collectively as *positional parameters.)*




  Let's see Appeal in action, with our first example.
  In all our examples we're going to assume your program
  is called `script.py`.  Let's say `script.py` looked like
  this:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def hello(name):
      print(f"Hello, {name}!")

  app.main()
  ```

  If you now ran `python3 script.py help hello`, you'd
  see usage information for your `hello` command.
  It'd start like this:

      usage: script.py hello name

  Already, a lot has happened!  Let's go over it piece by piece:

  * We created an `Appeal` object called `app`.
    This object will handle processing the command-line
    and calling the appropriate command function.
  * We decorated the function `hello()` with `@app.command()`,
    a method call on our Appeal object.
    This tells Appeal that `hello()` should be a
    *command*, using the name of the function as the
    command string, and translating the function's
    parameters into the command-line parameters.
    So our command-line command is called `hello`.
    We call a function decorated with `@app.command()`
    a *command function.*
  * Our `hello()` command function takes one positional
    parameters, `name`.  Therefore, our `hello` command
    on the command-line takes one positional argument,
    which we identify as `name` in the usage string.
  * Appeal also automatically created simple help for our
    program, displaying *usage* information.  Usage shows
    you what command-line options and arguments the command
    will accept.

  So!  If you ran this command at the command-line:

      % python3 script.py hello world

  Appeal would call your `hello()` function like this:

  ```Python
  hello('world')
  ```

  and you'd be rewarded with:

      Hello, world!

  The return value from your command function is the return
  code for your program.  If you return `None` or `0`, that's
  considered success; returning a non-zero integer indicates
  failure.  (And if your function exits without a return
  statement, Python behaves as if your function ended with
  `return None`.)




  Let's change up our example, and add an optional parameter:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(pattern, filename=None):
      print(f"fgrep {pattern} {filename}")

  app.main()
  ```

  Now our command is called `fgrep`, and it takes two parameters.
  The second one, `filename`, is optional, with a default value of `None`.

  You can of course specify both parameters yourself.  Running this:

      % python3 script.py fgrep WM_CREATE window.c

  results in Appeal calling your `fgrep()` function like this:

  ```Python
  fgrep('WM_CREATE', 'window.c')
  ```

  But you can also omit the `filename` parameter.
  If you run this command at the command-line:

      % python3 script.py fgrep WM_CREATE

  Appeal would call `fgrep()` like this:

  ```Python
  fgrep('WM_CREATE', None)
  ```

  Actually that's not 100% accurate.  When Appeal
  builds the arguments to call your `fgrep()` function,
  it only passes in the arguments you passed in on the
  command-line.  So actually Appeal calls your `fgrep()`
  function like this:

  ```Python
  fgrep('WM_CREATE')
  ```

  And it's Python that sets the `filename` parameter to `None`.

  What else can Appeal command functions do?  Well, they can
  have a `*args` parameter. Naturally, a command function that
  takes `*args` (internally called a *var_positional*
  parameter) can accept as many positional arguments as the
  user wants to supply.  Here's a demonstration:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(pattern, *filenames):
      print(f"fgrep {pattern} {filenames}")

  app.main()
  ```

  Now the user could pass in no filenames, one filename,
  fifty filenames--as many as they want!  They'd all be
  collected in a tuple and passed in to `fgrep()` in the
  `filenames` parameter.




  Now let's examine what Appeal does with keyword-only
  parameters.  Let's add three keyword-only parameters
  to our example:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(pattern, *filenames, color="", number=0, ignore_case=False):
      print(f"fgrep {pattern} {filenames} {color!r} {number} {ignore_case}")

  app.main()
  ```

  Now the `fgrep` command-line usage looks like this:

      usage: script.py fgrep [-c|--color] [-n|--number int] [-i|--ignore-case] pattern [filenames]...

  Again, a lot just happened.

  First, I'll remind you, keyword-only parameters
  are presented as options on the command-line.
  Appeal automatically took each keyword-only parameter,
  added `'--'` to the front of the parameter name,
  and turned that into an option.  (Also, if the parameter
  name has any underscores, Appeal turns those into dashes.)

  Second, Appeal also automatically uses the first letter of a
  keyword-only argument as a short option.  So the
  `color` keyword-only parameter becomes both the `--color`
  *and* `-c` options.  When running your program, the user
  can use `-c` or `--color` interchangably.  The same goes
  for `-i` and `--ignore_case`, and for `-n` and `--number`.

  (What if you have two keyword-only parameters that start
  with the same letter?  The first one gets the short option.
  If we added a keyword-only parameter named `credit` to the
  end of `fgrep()`'s parameter list, Appeal would map `color`
  to `--color` and `-c`, but only map `credit` to `--credit`.)

  Third, options are *always optional.*
  (As a pedantic wag might put it--"the clue's right there in the name.")
  Therefore, in Appeal, keyword-only
  parameters to command functions must *always* have a
  default value.  (Python programmers usually have default
  values for their keyword-only parameters anyway, so this
  requirement isn't a big deal.)

  Fourth, notice that `--color` takes an argument, or *oparg.*
  Appeal noticed that the `color` parameter had a default
  value of `""`--its default value is a `str`.
  So Appeal infers that you want the user to supply an oparg
  to `--color`.  If the user specifies `--color` on the
  command-line, it must be followed by an oparg, and Appeal
  will take the string off the command-line and pass it
  straight into the `color` parameter.

  Fifth, `--number` also takes an oparg, but it has a default of `0`.
  Appeal infers from that that `--number` should be an `int`.
  Appeal automatically converts the string from the command-line
  into a Python object for you, using the type of the default value.
  (Appeal did that for `--color` too--except `--color` takes a str,
  so no conversion is necessary.)  When the user provides an oparg
  to `--number` on the command-line, it must be followed by an
  oparg; Appeal will take that oparg, pass it in to `int`, then take
  the return value from `int` and pass it in to the `number` parameter.

  Finally, `ignore_case` has a default value of `False`.
  Boolean values for options are a special case: they don't
  take an oparg. All they do is negate the default value.
  So if the user specifies `-i` once on the command-line,
  Appeal would pass `True` in to the `ignore_case` parameter.

  (By the way, a default value of `None` is a second
  special case.  If a positional or keyword-only parameter
  has a default value of `None`, Appeal behaves as if the
  type of the default is `str`.  It consumes an argument
  or oparg from the command-line and passes it in unchanged
  to that parameter.)

  Let's put it all together!  If you ran this command at the command-line:

      % python3 script.py fgrep -i --number 3 --color blue WM_CREATE window.c

  Appeal would call `fgrep()` like this:

  ```Python
  fgrep('WM_CREATE', 'window.c', color='blue', number=3, ignore_case=True)
  ```

  And if you ran this command at the command-line:

      % python3 script.py fgrep --color green boogaloo

  Appeal would call `fgrep()` like this:

  ```Python
  fgrep('boogaloo', color='green')
  ```




  Many programs that support "commands" also have
  "global options".  Global options are options
  specified on the command-line *before* the command.
  For example, in the example command-line at the top
  of this document, `script.py` takes a `--debug`
  option specified before the command--which makes it
  a "global option".

  Appeal supports global options, too.  It's simple:
  write your command function like normal, but
  instead of decorating it with `Appeal.command()`, decorate
  it with `Appeal.global_command()`.  Appeal will process all
  those options before the command, and call your global
  command function.

  `Appeal.global_command()` also gets used for programs that
  don't use "commands".  Although the "command" command-line
  paradigm is popular these days, most programs don't bother
  with them.  For example, `ls`, `grep`, and... hey! `python`
  itself!  None of these programs support commands, but they
  all support command-line arguments and options.

  Naturally, Appeal supports this behavior.  Simply decorate
  one function with `Appeal.global_command()` and don't add
  any command functions.

  On the flip side of this coin, Appeal also supports
  *subcommands*.  This is a common feature of command-line
  parsing libraries, though it's rarely-used in practice.
  The idea is, your command can *itself* be followed by
  another command.

  To add a subcommand to your Appeal instance, just
  decorate your command function with two chained
  command calls, specifying the name of the existing
  command in the first call, like so:

  ```Python
  @app.command()
  def db(...):
      ...

  @app.command("db").command()
  def deploy(...):
      ...
  ```

  This adds a `deploy` subcommand under the `db` command.
  So now the whole command-line looks something like this:

      script.py [global arguments and options] db [db arguments and options] deploy [deploy arguments and options]

  Finally, what should Appeal do if your program
  takes commands, but the user doesn't supply one?
  That's what the *default command* is for.  The
  default command is a command function Appeal will
  run for you if your Appeal instance has commands,
  and the user doesn't supply one.  For example,
  if `script.py` has ten different commands, but the
  user just runs

      script.py

  without any arguments, Appeal would run the default
  command.

  If you don't specify a default command, Appeal has
  a built-in default *default command*.  The default *default
  command* raises a usage error, which means it prints basic help
  information and exits.

  To specify your own default command, just decorate a
  command function with the `Appeal.default_command()` decorator.
  For example, if you wanted your program to run the `status`
  command when the user didn't specify a command, you could
  do this:

  ```Python
  @app.default_command()
  def default():
      return status()
  ```

  Notice that the default command doesn't take any arguments
  or options.  It simply can't accept any, by definition.

  (If the user specified options
  without a command, they'd be considered "global options"
  and would be processed by the global command.  And if the
  user specified an argument, that would automatically be the
  name of the command to run.)

  And yes, subcommands can have a default command too:

  ```Python
  @app.command('db').default_command()
  def db_default():
      return db_status()
  ```



  Python 3 supports annotations for function parameters, meant
  to conceptually represent types.  Appeal supports annotations
  too; they explicitly tell Appeal what type of object a parameter
  requires.  For example:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(pattern, *filenames, id:float=None):
      print(f"fgrep {pattern} {filenames} {id}")

  app.main()
  ```

  Here `id` has a default value of `None`, but it also has
  an explicit annotation of `float`.   If the user uses `--id`
  on the command-line, it must be followed by an oparg,
  which Appeal will convert to a Python object by calling `float`.
  (And, as you can see, the annotation and the type of the default
  don't *necessarily* have to agree... although it's usually a
  good idea.)

  Although annotations are *meant* to represent types, Appeal
  actually accepts any callable--it can be a type, or a
  user-defined class, or just a regular function.  Appeal
  calls these annotations *converters.*

  Here's how Appeal decides on the converter for a parameter,
  from highest-priority to lowest-priority:

  * If the signature for that parameter has an annotation,
    Appeal uses the annotation as the converter.
  * If the signature for that parameter *doesn't* have an
    annotation, but *does* have a default value, Appeal
    will use `type(default)` as the converter in most cases.
    The exceptions:
    - If `type(default)` is `NoneType`, Appeal will use `str`
      instead.
    - If `type(default)` is `bool`, and the parameter is a
      keyword-only parameter, Appeal will use a special internal
      class that implements the special-case "negate the default"
      behavior for options with boolean default values.
  * If the signature for that parameter lacks both an annotation
    *and* a default value, Appeal uses `str` as the converter.

  Converters are surprisingly flexible.
  For example, Appeal will introspect the converter for a
  keyword-only parameter and map all its positional arguments
  into opargs.  That's how Appeal supports options that take
  *multiple opargs:* you simply annotate the keyword-only
  parameter with a converter that takes *multiple arguments.*
  Appeal will also pay attention to the annotations for the
  converter's own arguments, and use those to convert the
  strings from the command-line into Python objects.

  Let's tie it all together with another example:

  ```Python
  import appeal
  app = appeal.Appeal()

  def int_and_float(integer: int, real: float):
      return [integer*3, real*5]

  @app.command()
  def fgrep(pattern, *filenames, position:int_and_float=(0, 0.0)):
      print(f"fgrep {pattern} {filenames} {position}")

  app.main()
  ```

  Here, Appeal would introspect `fgrep()`, then also
  introspect `int_and_float()`.  The resulting usage
  string would now look like this:

      usage: script.py fgrep [-p|--position integer real] pattern [filenames]...

  `--position` takes *two* opargs.  Appeal would
  call `int` on the first one and `float` on the second
  one.  It would then call `int_and_float()` with those
  values, and the return value of `int_and_float()` would
  be passed in to the `position` parameter on `fgrep()`.

  So now if you ran:

      % python3 script.py fgrep -p 2 13 funkyfresh

  Appeal would call:

  ```Python
  fgrep('funkyfresh', position=[6, 65.0])
  ```

  Finally, let's change the example to demonstrate something
  else: although converters can be any callable, user-defined
  classes work fine too.  And Appeal can correctly infer the
  type based on the default value for any type.  So consider
  this example:

  ```Python
  import appeal
  app = appeal.Appeal()

  class IntAndFloat:
      def __init__(self, integer: int, real: float):
          self.integer = integer * 3
          self.real = real * 5

      def __repr__(self):
          return f"<IntAndFloat {self.integer} {self.real}>"

  @app.command()
  def fgrep(pattern, *filenames, position=IntAndFloat(0, 0.0)):
      print(f"fgrep {pattern} {filenames} {position}")

  app.main()
  ```

  This example behaves essentially the same as the previous example
  in this section, except the formatting of `position` is slightly
  different.  But the command-line usage is exactly the same!
  Appeal inferred the converter for `position` based on the type
  of its default value, then introspected that type to determine
  how many opargs it should consume from the command-line and how
  to convert them.


  > **An important note about annotations**
  >
  > If you use static type analysis in your project,
  > your static type analyzer may not enjoy analyzing Python
  > code using Appeal.  Static type analyzers are designed
  > to understand "type hints", a means of specifying static
  > type information introduced in Python 3.5 with the
  > `typing` module.  But Appeal doesn't use type hints,
  > and there are some ways Appeal uses annotations that
  > static type analyzers may not like.
  >
  > Fortunately, there are ways to get static type analyzers
  > to work alongside Appeal.
  >
  > First, you can decorate your Appeal command functions
  > and converters with `@typing.no_type_check()`.  This should
  > only be necessary if you use functions as annotations;
  > if you only ever use types and classes, this shouldn't be
  > necessary.
  >
  > Second, if you're using Python 3.9 or newer, you can use
  > `typing.Annotated` with your annotations.  `typing.Annotated`
  > allows you to specify an ordered list of values, and static
  > type hints only ever use the *first* value.  Appeal also
  > handles `typing.Annotated`, but Appeal only ever uses the
  > *last* value.  This makes it easy--you can have both types
  > of annotations, side by side, and both static type checkers
  > and Appeal are perfectly happy.




  You can use almost any function as an annotation...
  within reason.  Appeal will introspect your annotation,
  determine its input parameters, and call it to convert
  the command-line argument into the argument it passes
  in to your command function.

  For example, what if you wanted an option that accepted
  a string which gets broken up based on a delimiter substring?
  This is a common idiom for `configure` scripts on UNIX-like
  platforms; for example,
  [Python's own `configure` script](https://github.com/python/cpython/blob/3.9/configure)
  supports this option:

      --with-dbmliborder=db1:db2:...

  Happily that's easy to do in Appeal.  Just write a converter
  function that accepts a string, breaks it into substrings
  however you like, and returns the list.

  Although... you don't need to bother!  Appeal also provides
  a converter that does it for you, called `appeal.split()`.
  You pass in as many delimiter strings as you want, and
  `appeal.split()` will split the command-line across all of
  them.  (If you don't specify any delimiters, `appeal.split()`
  will split at every whitespace character.)




  One thing you might have noticed by now: the interfaces
  you've seen only allow Appeal to handle command-lines
  where an option can be specified either zero times or
  one time.  What if you want the user to be able to
  specify an option three times?  Or ten?  That's what the
  `MultiOption` class is for.  `MultiOption` objects
  are converters that allow options to be specified
  multiple times.

  `MultiOption` isn't useful by itself; it's only an
  abstract base class.  To make use of it you'll
  need to use a subclass--or create your own.

  This time, let's start with some examples.  Appeal
  provides three useful subclasses of `MultiOption`:
  `counter`, `accumulator`, and `mapping`.

  First, let's look at `counter`.  `counter`
  simply counts the number of times an option is
  specified on the command-line.  This is a somewhat
  common idiom for "verbose" options; a program
  that supports `-v` to mean *verbose* may allow
  you to specify `-v` more than once to make
  it *more* verbose.  Here's how you'd do that
  with Appeal:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(*, verbose:appeal.counter()=0):
      print(f"fgrep verbose={verbose!r}")

  app.main()
  ```

  If the user ran

      % python3 script.py fgrep

  Appeal would call

  ```Python
  fgrep()
  ```

  allowing Python to pass in the default value of `0` to `verbose`.
  And if the user ran

      % python3 script.py fgrep -v --verbose -v

  Appeal would call

  ```Python
  fgrep(verbose=3)
  ```

  `accumulator` handles options that take a single oparg.
  It remembers them all and returns them in a single array.
  Like so:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(*, pattern:appeal.accumulator=[]):
      print(f"fgrep pattern={pattern!r}")

  app.main()
  ```

  If the user ran

      % python3 script.py fgrep --pattern three -p four --pattern fiv5

  Appeal would call

  ```Python
  fgrep(pattern=['three', 'four', 'fiv5'])
  ```

  What if you don't want strings, but another type?  Using crazy
  science magic from the future, `accumulator` is actually
  parameterized.  You can say:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def fgrep(*, pattern:appeal.accumulator[int]=[]):
      print(f"fgrep pattern={pattern!r}")

  app.main()
  ```

  and now the opargs to `--pattern` will all be converted using int.

  You can even specify multiple types as arguments to the
  parameterized version of `accumulator`, separated by commas.
  The option will then require multiple opargs and convert
  them to the types specified.

  `mapping` is like `accumulator` except it returns a
  `dict` instead of a `list`.  An option annotated with `mapping()`
  consumes *two* positional arguments from the command-line;
  the first one is the key, the second one is the value.
  (You can also parameterize `mapping` the same way you parameterize
  `accumulator`, though you can only specify exactly two types.)


  Of course, you can also subclass `MultiOption` to make your own
  converter classes with custom behavior. `MultiOption` subclasses
  can override these three methods:

  ```Python
  class MultiOption:

      def init(self, default):
          ...

      def option(self, ...):
          ...

      def render(self):
          ...
  ```

  Well, actually, subclasses are *required* to override
  `option()` and `render()`.  But `init()` is optional.

  If you then specify a subclass of `MultiOption` as an
  annotation on a keyword-only parameter of an
  Appeal command function, several things happen:

  * If that option is specified one or more times on
    the command-line, Appeal will instantiate exactly
    one of these objects and call its `init()` method.
  * Every time the user specifies that option on
    the command-line, Appeal will call the `option()`
    method on the object.
  * After finishing processing the command-line,
    Appeal will call the `render()` method on the
    object, and pass the value it returns as the
    argument to that keyword-only parameter.

  The most powerful part of this interface: you can
  redefine `option()` to suit your needs--it supports
  the same sort of polymorphism as annotations do.
  Appeal will introspect your `option()` method to
  determine how many opargs to consume from the
  command-line, and how to convert them.

  Let's demonstrate all this with another example.
  If you want your option to take two opargs,
  with one being an `int` and the other being
  a `float`, you would define `option()` in your
  subclass as:

  ```Python
  class MyMultiOption(appeal.MultiOption):

      def option(self, a:int, b:float):
          ....
  ```

  Every time the user specified your option,
  it would take two opargs, and they would be
  converted into an `int` and a `float` before
  calling your `option()` method.  It's up to
  you to decide how to store them, and how to
  render them into a single value returned
  by your `render()` method.

  `MultiOption` is a subclass of a general
  `Option` class.  `Option` behaves identically
  to `MultiOption`, except it only permits
  specifying the option once on the command-line,
  which means it will only your `option()`
  method once.
  You usually don't need to bother with making subclasses
  of `Option`--it's usually better to just use a class
  directly, like our `class IntAndFloat` example.
  The only feature you get by subclassing `Option` is,
  you get the default value for the parameter passed in
  to your constructor.

  (The downside of subclassing `Option` and `MultiOption`
  is that it makes exporting your Appeal API as an automation
  API a little less convenient for the user, because your
  users will have to construct these objects and feed
  values into them by calling the `option` method.)




  What if you want to restrict the data the user provides
  on the command-line?  That's simple, just use a converter!
  Appeal provides a couple sample converters for data validation,
  but it's easy to write your own.

  The classic example is a parameter where you can only use one
  of a list of values.  For that, you can use Appeal's `validate()`
  converter.  For example, this command restricts the `direction`
  parameter to one of six canonical directions:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  def go(direction:appeal.validate('up', 'down', 'left', 'right', 'forward', 'back')):
      print(f"go direction={direction!r}")

  app.main()
  ```

  You can pass in an explicit type using a `type=`
  named argument to `validate()`; if you omit it,
  it uses the type of the first argument.

  Appeal also has a built-in range validator
  called `validate_range()`.  It takes `start`
  and `stop` arguments the same way Python's
  `range()` function does. Note that `validate_range()`
  differs from Python's `range()` in one subtle way:
  values *equal* to `stop` are allowed.

  If you prefer, you can "clamp"
  the value the user passed in to the range,
  by supplying the argument `clamp=True` to
  `validate_range()`. In that case, if the value
  the user specifies is outside the range, `validate_range()`
  will return the closest value of either `start` or `stop`.

  (That's why `validate_range()` allows the
  value to be *equal to* `stop`.  `clamp` would
  be annoying to use if `stop` itself was an
  illegal value--particularly if the types
  were floats.)

  Appeal validation functions are straightforward to write.
  So, if these are insufficient to your needs,
  you can easily write your own.  Take a look
  at the implementations of `validate()` and
  `validate_range()` inside Appeal to see one way to do it!




  Some programs have a set of options on their
  command-line that are mutually exclusive.  Consider
  this simple-minded command-line:

      go [--north|--south|--east|--west]

  That is, you want the user to be able to "go" in
  one of those four directions, but *only* one.
  How would you do that in Appeal?

  Easy.  You simply define multiple options that
  write to the same parameter.  All the behavior
  you've seen so far is using the *default* way of
  mapping keyword-only parameters to options.  But
  actually Appeal allows you to make your own mappings.
  You can map a parameter as many ways as you want,
  even using different converters!

  To manually define your own options, use the `Appeal.option()`
  method on your Appeal instance.  It's a decorator you
  apply to your command function.  The first argument is
  the name of the parameter you want the option to write
  to.  After that is one or more options you want to
  map to this parameter.  `Appeal.option()` also takes
  `default` and `annotation` keyword-only parameters,
  allowing you to specify respectively the default value or
  annotation for this option.

  Here's a simple example of how to implement the above `go`
  command with Appeal:

  ```Python
  import appeal
  app = appeal.Appeal()

  @app.command()
  @app.option("direction", "--north", annotation=lambda: "north")
  @app.option("direction", "--south", annotation=lambda: "south")
  @app.option("direction", "--east",  annotation=lambda: "east")
  @app.option("direction", "--west",  annotation=lambda: "west")
  def go(*, direction='north'):
      print(f"go direction={direction!r}")

  app.main()
  ```

  All these annotations return a string.  But actually you can
  return any type you want--and you can even map multiple
  annotations that return different types to the same parameter.
  You can even annotate with a `MultiOption` to allow specifying
  that option multiple times!

  Note that, whenever you use the `option()` decorator
  to map your own options onto a parameter, Appeal won't add
  its default options for that parameter.  It'll only have
  the options you explicitly set.  Which means, for example,
  that in the sample code above, there aren't any short options
  for the options we created.  `-n` won't work, only `--north`.

  One final thing.  Your command function can accept `**kwargs`
  too.  The only things that will go into it are options you
  create with `Appeal.option()`, which map to parameters that
  don't otherwise exist.




  You already know that you can pass in a converter that takes
  multiple arguments, and Appeal will consume multiple arguments
  from the command-line to fill it.  And if the arguments to that
  converter have annotations, Appeal will call those functions to
  convert the command-line argument into the type your converter
  wants.

  But what if you did... *this?*

  ```Python
  import appeal
  app = appeal.Appeal()

  def int_float(i: int, f: float):
      return (i, f)

  def my_converter(i_f: int_float, s: str):
      return [i_f, s]

  @app.command()
  def recurse(a:str, b:my_converter=[(0, 0), '']):
      print(f"recurse a={a!r} b={b!r}")

  app.main()
  ```

  The `my_converter()` parameter `i_f` is a positional
  parameter with an annotation that, itself,
  *takes two positional parameters.*

  Would it surprise you to know--yes, it actually works!

  Converters have been fully recursive this *whole time.*
  Actually this fact has been hiding in plain sight
  all along--all the examples using `int_and_float()` are recursive
  too, because `int_and_float()` has parameters annotated with
  `int` and `float`.  Of course, those functions only take
  a single string argument; `my_converter()` takes two
  annotated positional parameters.
  But the principles remain the same.

  Still, this is a more complex situation than we've seen before.
  `recurse` takes a positional parameter `b` that has a default
  value, but its converter takes multiple positional parameters,
  and one of those *also* has a converter that takes multiple
  positional parameters.  How does Appeal map this to the
  command-line?

  Appeal "flattens" the tree of converter functions into a linear
  series of arguments and options.  In this case the usage string
  would look like this:

      recurse a [i f s]

  This tells you the `recurse` command takes either one or four command-line
  arguments.  That optional group of three command-line arguments
  has a special name in Appeal: it's an "argument group".
  Technically, Appeal views this command-line as taking two
  "argument groups": the first group is required, and consumes
  one command-line argument; the second group is optional, and
  consumes three command-line arguments.

  (We actually saw our first "argument group" in the
  second example in the
  **Annotations And Introspection** section above, but
  that time the argument group was an oparg.)

  Now let's add an option and see what changes:

  ```Python
  import appeal
  app = appeal.Appeal()

  def int_float(i: int, f: float):
      return (i, f)

  def my_converter(i_f: int_float, s: str, *, verbose=False):
      return [i_f, s, verbose]

  @app.command()
  def recurse2(a:str, b:my_converter=[(0, 0), '', False]):
      print(f"recurse2 a={a!r} b={b!r}")

  app.main()
  ```

  Now the usage looks like this:

      recurse2 a [i [-v|--verbose] f s]

  Notice the way Appeal renders it in the usage
  string--the options aren't created until *after* the first
  argument in the optional argument group.  This may seem
  strange but that's how it works.  That's how it *has* to work.

  Why?  From a high conceptual level, Appeal doesn't know that
  you've "entered" the optional argument group until it
  sees the user supply the first argument for that group.
  So it doesn't create the options defined in that group
  until after the first argument.

  This high conceptual level corresponds exactly to how Appeal
  calls your function.  Consider, if the user runs this command:

      recurse2 xyz

  Appeal calls your function like so:

  ```Python
  recurse2('xyz')
  ```

  Since Appeal never called `my_converter()`, it can't
  map `--verbose`.  It can only map `--verbose` once it
  knows it's going to call `my_converter()`, and that
  only becomes true the moment you supply that second
  command-line argument.

  Once you *do* supply that second command-line argument,
  you have to supply two more, for a total of four.

      recurse2 pdq 1 2 xyz

  Appeal calls your function like so:

      recurse2('pdq', my_converter(int('1'), float('2'), xyz))

      recurse2 pdq 1 2 xyz

  And in this example, you can supply the `-v` or `--verbose` anywhere *after*
  the second parameter.  So if your command-line looks like this:

      recurse2 pdq 1 2 xyz -v

  Appeal calls `recurse()` like this:

  ```Python
  recurse2('pdq', my_converter(int('1'), float('2'), xyz, verbose=True))
  ```


  Take a look back at all the examples in this document, and consider
  that anywhere you specify a function or type, you can pass in nearly
  any callable you like.

  For example, the parameterized version of `mapping` isn't limited just to simple types.
  If you used `mapping[str, int_float]` as the annotation
  for a keyword-only parameter, that option would consume
  three arguments on the command line: a `str`, an `int`, and
  a `float`, and the dictionary would map strings to 2-tuples
  of ints and floats.

  Now you're starting to see how powerful Appeal's converters
  really are!




  But recursive converters are just the beginning!

  > Buckle your seatbelt, Dorothy--because Kansas is going bye-bye.
  >
  > --Cypher, "The Matrix" (1999)



  What if you did... *this?*

  ```Python
  import appeal
  app = appeal.Appeal()

  def my_converter(a: int, *, verbose=False):
      return [a, verbose]

  @app.command()
  def inception(*, option:my_converter=[0, False]):
      print(f"inception option={option!r}")

  app.main()
  ```

  Woah, that works too!  We've created an option that
  *itself* takes an option.  If you run `fgrep --option`,
  you can now also specify `-v` or `--verbose`--but only
  *after* you've specified `--option`.

  In case you're wondering: `Appeal.option()` must
  decorate the function that takes the parameter you're
  mapping an option *to.*  So if you want to define
  explicit options for the `verbose` parameter to
  `my_converter` in the above example, you'd
  decorate `my_converter` with `Appeal.option()` calls,
  not `inception`.  (This also means, everywhere you
  use `my_converter` as a converter, it will behave
  the same, including taking the same options.)



  We're just getting started!  How about this:

  ```Python
  import appeal
  app = appeal.Appeal()

  def my_converter(a: int, *, verbose=False):
      return [a, verbose]

  @app.command()
  def repetition(*args:my_converter):
      print(f"repetition args={args!r}")

  app.main()
  ```

  That works too, and I bet you're already guessing what it
  does.  This version of `weird` accepts as many `int` arguments
  as the user wants to specify on the command-line, and *each one*
  can optionally take its own `-v` or `--verbose` flag.



  I'll give you one more crazy example:

  ```Python
  import appeal
  app = appeal.Appeal()

  class Logging:
      def __init__(self, *, verbose=False, log_level='info'):
          self.verbose = verbose
          self.log_level = log_level

      def __repr__(self):
          return f"<Logging verbose={self.verbose!r} log_level={self.log_level}>"

  @app.command()
  def mixin(log:Logging):
      print(f"mixin log={log!r}")

  app.main()
  ```

  Can you guess what usage for `mixin` looks like?  (Probably!)
  It looks like this:

      mixin [-v|--verbose] [-l|--log-level str]

  Even though `log` is a positional parameter, it doesn't consume
  any positional arguments on the command-line.  The `Logging`
  converter only adds options!  This is what object-oriented
  programmers might call a "mix-in".  With the `Logging` converter,
  you can add logging options to every one of your commands, without
  having to re-implement it each time.  (Though in most cases it's
  probably better to add such options to a global command function.)

  Internally this works exactly like you'd expect.  Since the
  `log` parameter consumes no command-line arguments, Appeal will
  always call its converter.  Specifying any of the options will
  set arguments for that call.  And the resulting `Logging` object
  will be passed in as the argument to `log`.


  What's really going on here is that, from Appeal's perspective,
  *there's no difference between a "command function" and a
  "converter".*  A command function is just a converter that
  happens to be mapped to a command.  So anything you can do
  with a command function, you can do with a converter too.
  A converter can define options, it can be decorated with
  `app.option()` (or `app.parameter()` which we haven't
  discussed yet), it can have accept any kind of parameter defined
  by Python, and any parameter can use (almost) any converter.
  And those converters can recursively use other converters.

  Really, anything can be used with anything:

  * Converters for positional parameters
    can take positional parameters, or keyword-only parameters, or `*args`, or `**kwargs`.
  * Converters for keyword-only parameters
    can take positional parameters, or keyword-only parameters, or `*args`, or `**kwargs`.
  * Converters for `*args`
    can take positional parameters, or keyword-only parameters, or `*args`, or `**kwargs`.
  * Command functions can use any converter.
  * The global command function can use any converter.

  By *now* you can see the expressive power Appeal gives you.
  Of course, you'll rarely use only a fraction of that power.
  But it's reassuring to know that, whatever command-line API
  metaphor you want to express, it's not just *possible* in
  Appeal--it's *easy.*




  Maybe you've noticed--all the examples so far have used
  standard Python functions as Appeal commands.  What about
  method calls?  Can you use those for commands?  The answer
  is, yes of course!  But it's slightly more complicated.

  Appeal's whole purpose in life is to call functions by
  pulling data from the command-line.  Whenever it sees a
  positional parameter on a function, it thinks "okay, I'm
  gonna have to supply an argument to that".  So if you map
  an *unbound* method call to a command:

  ```Python
  class MyApp:
      @app.command()
      def sum(self, *operands: int):
          return sum(*operands)
  ```

  Appeal would see the `self` parameter and think "aha! I
  need to pass a string in there!"  We need to prevent
  Appeal from seeing that parameter in the first place.

  There are two major techniques to handle this.  The first
  is straightforward, if a bit inflexible: create the instance
  of your class first, then call `app.command()()` on the
  bound instances.  Like this:

  ```Python
  app = appeal.Appeal()
  class MyApp:
      def sum(self, *operands: int):
          return sum(*operands)

  o = MyApp()
  app.command()(o.sum)
  app.main()
  ```

  Since you pass in the already-bound method to Appeal, it doesn't
  even *see* the `self` parameter in the signature.  (The signature
  of a bound method doesn't include the `self` parameter.)

  This works fine... but maybe it looks a little weird.  We're no
  longer decorating functions (or methods), instead we're calling
  the decorator function directly and passing in the bound method.
  It also restricts us to one instance of `MyApp` per Appeal
  instance, which might be restrictive.

  The other technique uses a little magic to provide a convenient
  and familiar-looking interface.  `Appeal.app_class()` gives you
  two decorators; you use one to decorate your class, and the
  other to decorate methods in the class.  Appeal will instantiate
  your class for you, and use your `__init__` method as your app's
  "global command" to handle global options!

  ```Python
  import appeal

  app = appeal.Appeal()
  app_class, command_method = app.app_class()

  @app_class()
  class MyApp:
      def __init__(self, *, verbose=False):
          print(f"MyApp init verbose={verbose!r}")
          self.verbose = verbose

      def __repr__(self):
          return "<MyApp>"

      @command_method()
      def add(self, a, b, c):
          print(f"MyApp add self={self!r} a={a!r} b={b!r} c={c!r} self.verbose={self.verbose!r}")

  app.main()
  ```

  Behind the scenes, this uses a `CommandMethodPreparer` object
  to handle late-binding the method to the object.  Since
  `Appeal.app_class()` is a little inflexible, you may want
  to use these objects directly.  You can create one manually
  by calling `Appeal.command_method()`.  Here's an example showing
  how to use one:

  ```Python
  import appeal

  app = appeal.Appeal()
  command_method = app.command_method()

  class MyApp:
      def __init__(self, id):
          self.id = id

      def __repr__(self):
          return f"<MyApp id={self.id!r}>"

      @command_method()
      def add(self, a, b, c):
          print(f"MyApp add self={self!r} a={a!r} b={b!r} c={c!r}")

  my_app = MyApp("dingus")

  p = app.processor()
  p.preparer(command_method.bind(my_app))
  p.main()
  ```

  This is the first time you're seeing the `Processor`
  object.  All the runtime information for processing
  a command-line lives in the `Processor` object; in
  fact, `Appeal.main` and `Appeal.process` are both
  thin wrappers over their equivalent methods on the
  `Processor` object.  Moving all the runtime information
  into the `Processor` object lets you process multiple
  command-lines with the same Appeal object, even
  simultaneously!

  The `CommandMethodPreparer` object is at the core of how Appeal
  handles late-binding of methods to objects.  First,
  you decorate the method calls of your class with this object.
  You then call the `bind` method on that object to pass in the
  instance of that class you want to bind those methods to--though
  `app_class()` takes care of that for you.  `bind()` returns a callable
  you pass in to `Processor.preparer`, which binds the method to that
  instance before Appeal calls it.

  Under the covers, `CommandMethodPreparer` wraps the method
  with a `functools.partial` object, passing in a placeholder
  object for the `self` parameter.  Then `command_method.bind()`
  replaces the placeholder for the real instance.  For maximum
  compatibility, it actually uses `getattr()` to bind the
  instance to the method.



  Appeal automatically generates *usage* for your command functions.
  But it's up to you to write the documentation explaining what those
  commands and arguments and options actually *do.*

  There's very complete notes on how to write documentation in Appeal,
  see `appeal/notes/writing.documentation.txt` in the Appeal source
  distribution.  In a nutshell, you write docstring in a particular way,
  and Appeal can mechanically parse them and combine them together.
  So you document each converter separately, and Appeal smooshes all
  these bits of documentation together to produce the help for your
  command function.

  (One note: the main help for your program should be the docstring
  for your Appeal instance's global command.)




  Appeal allows for friction-free command-line APIs.  You write your
  command function, point Appeal at it, and whoosh! now you've got a
  command-line interface.  But there are other interfaces users may
  want to use to configure your program.  Now Appeal can work with
  those too.

  For example, your program may read configuration from environment
  variables.  Some programs launch an editor; for example `git` will
  open an editor when committing a revision.  Traditionally on
  UNIX-based platforms this is configurable using two environment
  variables, `VISUAL` and `EDITOR` in that order of preference.

  Appeal doesn't need to add explicit support for environment variables,
  as Python already has an easy-to-use interface.  For example, here's
  how to support the environment variables configuring your editor:

  However, many programs also support a configuration file, also called
  an "rc file"  on UNIX.  By convention settings in such a config file
  usually take precedence over environment variables.  For example, you
  can configure what editor `git` uses for commits with a value called
  `core.editor` stored in a config file.

  As of 0.6, Appeal has support for reading data from configuration
  files.  Note that Appeal doesn't read the data files itself; you
  already have a library for that.  Instead, Appeal has a generic
  mechanism for reading data from either an iterable or a mapping--
  either a list or a dict.

  The first step is to read in the values from the configuration file,
  and produce a dict or dict-like object.  You can use any library
  you like.  For example, the [`tomli`](https://pypi.org/project/tomli/) library
  works well for [TOML files.](https://en.wikipedia.org/wiki/TOML)
  JSON and YAML parsers also work nicely.  And this facility works
  *especially* well with my [`Perky`](https://pypi.org/project/perky/)
  file format.  Though that's just a coincidence, as they were designed
  separately, years apart.  Honest!

  (You can also use `configparser` to read your INI config file,
  but this doesn't mesh well with Appeal's model.  Better support
  for reading INI files is a possible future direction for Appeal.)

  Once you've got a dictionary containing your configuration information,
  you can get Appeal to read from it using a single method call:

  ```Python
      Appeal.read_mapping(self, callable, mapping)
  ```

  Simply pass in the callable you want called, and the mapping--the dict--you
  read from your config file.  Appeal will read the names of the callable's
  parameters, pull values out of the mapping using those names, and pass those
  values in to a call to the callable.

  Of course, any mapping will work.  But this method works particularly
  well with classes decorated with `dataclasses.dataclass`.  In just a few
  lines, you can define a class to contain your configuration information,
  read it out of a file, and populate the class with values of all the correct
  types!

  In a lot of ways, this works very similarly to Appeal when it's processing
  a command-line.  For example:

  * Appeal will use the annotations and default values to convert
    the values from the dictionary into the correct types.
  * Parameters with default values are optional; parameters without
    default values are required.

  But there are differences too:

  * You can use positional-only, positional-or-keyword,
    or keyword-only arguments.  However, var-positional
    (`*args`) and var-keyword (`**kwargs`) are unsupported.


  Let's bring all of this together with an example.  Let's say we're
  writing a hypothetical program that may launch an editor.  Our
  sophisticated program has *five* ways to decide what program
  to run for the editor.  In decreasing order of importance:

  * Command-line options '-e' and '--editor' specify the
    editor to use for this instnace.
  * The config file `~/.myprogramrc` is a Perky file, and it
    can contain an `editor` value.
  * If the user has set an `VISUAL` environment variable, use that.
  * If the user has set an `EDITOR` environment variable, use that.
  * The default value is `/usr/bin/vi`.

  Here's sample Python code implementing those semantics:

  ```Python
      default_editor = os.environ.get("VISUAL",
          os.environ.get("EDITOR", "/usr/bin/vi"))
      @dataclasses.dataclass
      class ConfigFile:
          editor:str=default_editor

      d = perky.load(os.path.expanduser("~/.myprogramrc"))
      app = appeal.Appeal()
      config_file = app.read_mapping(ConfigFile, d)

      @app.global_command()
      def global_command(*, editor=config_file.editor):
          print(f"editor = {editor}")

      app.main()
  ```

  Note: using `os.path.expanduser` and a hard-coded filename
  like this is no longer considered best practice.  You should use
  [`platformdirs`](https://pypi.org/project/platformdirs/) to
  define the paths to your config files.



  Appeal's config file reader supports reading values from nested dicts.
  This maps directly onto nested function calls in annotations.  If an
  annotation takes two or more parameters, the name of the parameter
  with that annotation will be used as the name of the nested dict.

  Since that probably wasn't clear--sorry!--an example would probably help.
  Consider this example dictionary:

  ```Python
      d = {
          'a': 33,
          'b': {
              'verbose': True,
              'color': 'blue',
          },
      }
  ```

  Here the value of `'b'` is a nested dict.  If we want Appeal to read
  a dict with this shape, it will have to descend into that nested dict.
  Appeal does that by default when a parameter has an annotation, and
  the annotation takes two or more parameters.  Here's sample Python
  showing how to read this dict using Appeal:

  ```Python
      def read_b(verbose=False, color='black'):
          return (verbose, color)

      def config_file(a: int, b: read_b):
          return (a, b)
  ```

  Because `read_b` is an annotation taking multiple parameters,
  Appeal will assume the value of `'b'` is a nested dict,
  and will get the values of `'verbose'` and `'color'` from
  that dict.

  If you don't want this behavior, you can disable it by
  decorating the annotation function with the `unnested`
  method on the Appeal object.  If we change the code to
  the following:

  ```Python
      @app.unnested()
      def read_b(verbose=False, color='black'):
          return (verbose, color)

      def config_file(a: int, b: read_b):
          return (a, b)
  ```

  Appeal *won't* descend into a nested dict named `'b'`.
  In this case the dictionary would have to be be completely
  flat, like this:

  ```Python
      d = { 'a': 33, 'verbose': True, 'color': 'blue' }
  ```



  Appeal can also read from iterables inside the dictionary.
  The parameter that accepts an iterable should be annotated
  with a subclass of `MultiOption`.  Appeal will instantiate
  the `MultiOption` and use the MultiOption protocol to
  fill the object.

  For example, if your config file dict looked like this:

  ```Python
      d = {
          'color': 'blue',
          'lines': [
              'line 1',
              'here is line 2',
              'and finally, line 3',
              ]
      }
  ```

  Appeal could map it to this callable:

  ```Python
      @dataclasses.dataclass
      class ConfigFile:
          lines: appeal.accumulator
          color:str = ''
  ```

  If the `MultiOption` option takes multiple parameters,
  then the list must contain dictionaries.  For example,
  this Python code:

  ```Python
      class Resolutions(appeal.Multioption):
          def init(self, default=None):
              self.default = default
              self.values = None

          def option(self, width:int, height:int, depth:int):
              if self.values is None:
                  self.values = []
              self.values.append((width, height, depth))

          def render(self):
              if self.values is None:
                  return self.default
              return self.values

      @dataclasses.dataclass
      class ConfigFile:
          resolutions: Resolutions
          color:str = ''
  ```

  Would be able to read this mapping:

  ```Python
      d = {
          'color': 'orange',
          'resolutions': [
              {'width': 1280, 'height': 1024, 'depth': 24},
              {'width': 1600, 'height': 1200, 'depth': 16},
          ],
      }
  ```




  In addition to Appeal's `read_mapping` method, Appeal
  also supports a `read_iterable` method.  The API is
  almost identical:

  ```Python
      Appeal.read_iterable(callable, iterable)
  ```

  However this function is much simpler.  The iterable
  should itself be an iterable of iterables.  Appeal will
  call the callable you specify once for every nested
  iterable.  The result will be appended to a list, and
  `read_iterable` will return that list.

  As always, Appeal will handle converting values using
  the annotations you specify.  Unlike `read_mapping`,
  here you may pass in a function accepting `*args`,
  in which case Appeal will process an arbitrary number
  of trailing arguments.  Also unlike `read_mapping`,
  there's no support for testing--neither nested dictionaties
  nor (further) nested lists.  (Like when processing the
  command-line, when reading values from the iterable,
  nested annotations get flattened.)



  Finally, Appeal has special support for reading CSV files.  This may
  seem like an odd thing to support--nobody uses CSV files as config
  files.  But CSV files were the proof-of-concept for Appeal's config
  file support, and it's proved useful in another project, so for
  now it's staying in.  There's a special method for reading CSV
  files:

  ```Python
      Appeal.read_csv(self, callable, csv_reader, *, first_row_map=None)
  ```

  You pass in your callable, and a fresh `csv.reader` object.  Appeal
  will read the rows out of the `CSV` object, passing in the strings into
  the `callable`, and append the result to a list.  The return value is
  that list.

  If `first_row_map` is false, `read_csv` will ignore the first line of
  the CSV file (the "column names" line) and pass in the values from the
  CSV file by position.  If `first_row_map` is true, `read_csv` will use
  the rows from the first line of the CSV file as keys in a dictionary,
  populate the values with each subsequent row, and will pass the
  arguments by name.

  In other words, if `first_row_map` is false, Appeal calls

  ```Python
      callable(*row)
  ```

  for every line after the first line in the CSV file.  And if `first_row_map`
  is true, Appeal calls

  ```Python
      d = {key: value for key, value in zip(column_headers, row)}
      callable(**d)
  ```

  for every line after the first line in the CSV file.




  `Appeal(help=True, version=None, positional_argument_usage_format="{name}", default_options=default_options)`

  Creates a new Appeal instance.

  If `help` is true, Appeal automatically adds help support to
  your program:

  * Adds hard-coded `-h` and `--help` options that print basic help.
  * If your Appeal instance has any commands, and you haven't defined
    a `help` command, automatically adds a `help` command.

  If `version` is true, it should be a string denoting the version
  of your program.  Appeal will automatically add version support
  to your program:

  * Adds hard-coded `-v` and `--version` options that print
    the version string.
  * If your Appeal instance has any commands, and you haven't defined
    a `version` command, automatically adds a `version` command
    which prints the version string.

  `positional_argument_usage_format` is the format string used
  to format positional arguments for usage.  The only valid
  interpolations inside this string are `{name}`, which evaluates
  to the name of the parameter, and `{name.upper()}`, which evaluates
  to the upper-cased name of the parameter.  So if you want your usage
  string to show arguments or opargs as `<name>` or `NAME`, you can
  achieve that by setting `positional_argument_usage_format` to
  `<{name}>` or `{name.upper()}` respectively.

  `default_options` is a callable, called when a keyword-only parameter
  for a command function or a converter doesn't have any options
  explicitly mapped to it.  The purpose of `default_options` is to
  call `Appeal.option()` one or more times to create some default options
  for that keyword-only parameter.

  The API for a `default_options` callable should be:

      default_options(appeal, callable, parameter_name, annotation, default)

  * `appeal` is the Appeal instance.
  * `callable` is the command function or converter the parameter is defined on.
  * `parameter_name` is the name of the keyword-only parameter that does
     not have any explicitly defined options.
  * `annotation` is the annotation for this parameter.  This may
     be explicitly set on the function, or it may be inferred from the
     default parameter.
  * `default` is the default value for this parameter.  Since Appeal
     requires that keyword-only parameters must always have default values,
     this may never be `inspect.Parameter.empty`.

  The return value of `default_options` is ignored.

  The default value of `default_options` is `Appeal.default_options()`,
  documented below.


  `Appeal.command(name=None)`

  Used as a decorator.  Returns a callable that accepts a single
  parameter `callable`, which must be a callable.

  Adds the callable as a command
  for the current Appeal instance.  If `name` is `None`, the name of
  the command will be `callable.__name__`.

  (Doesn't modify `callable` in any way.)


  `Appeal.global_command()`

  Used as a decorator.  Returns a callable that accepts a single
  parameter `callable`, which must be a callable.

  Sets the *global command* for this Appeal object.  This is
  the command that processes global options before the first
  command function.

  Can only be set on the topmost Appeal object.  (You can't
  call `app.command('foo').global_command()`.)

  (Doesn't modify `callable` in any way.)


  `Appeal.default_command()`

  Used as a decorator.  Returns a callable that accepts a single
  parameter `callable`, which must be a callable.

  Sets the *default command* for this Appeal object.  The default
  command is run when your Appeal instance has subcommands,
  but the user doesn't supply the name of a command on the command-line.

  Your default command function must not take any parameters.

  (Doesn't modify `callable` in any way.)


  `Appeal.option(parameter_name, *options, annotation=empty, default=empty)`

  Used as a decorator.  Returns a callable that accepts a single
  parameter `callable`, which must be a callable.

  Maps an option on the command-line to the parameter `parameter_name`
  on the decorated function.  All subsequent positional parameters
  are options, like `--verbose` or `-v`.  (Thus, they must be strings,
  either exactly two characters long, or four or more characters long.)

  `annotation` is the converter that will be used if this
  option is invoked.  If no explicit `annotation` is supplied,
  `Appeal.option()` will default to `type(default)`.

  `default` is the default value for this option.  Since this parameter
  only comes into play if the user specifies this option, a `default` value
  here is nearly useless.  But it does have two uses:

  * If the type of the annotation is a subclass of `Option`, this default
    value will be passed in to `Option.init()`.
  * If no `annotation` is specified, the annotation defaults to
    `type(default)`.

  It's illegal to call `Appeal.option()` without specifying a value
  for either `annotation` or `default`.

  Raises `AppealConfigurationError` if any `option` has already been
  mapped inside this `Appeal` instance *with a different signature.*

  (Doesn't modify `callable` in any way.)


  `Appeal.parameter(self, parameter_name, *, usage=None)`

  Used as a decorator.  Returns a callable that accepts a single
  parameter `callable`, which must be a callable.

  Allows for configuration of a positional (or positional-or-keyword)
  parameter on a command function or converter.  `parameter_name` is the
  name of the parameter; it must be a parameter of the decorated `callable`.

  Currently the only supported configuration is `usage`, which specifies
  the string that will represent this parameter in usage information.

  (Doesn't modify `callable` in any way.)


  `Appeal.main(args=None)`

  Processes a command-line and calls your command functions.
  Stops at the first failure result and passes it in to `sys.exit()`.
  Catches usage errors; if it catches one, displays usage information.
  The implementation calls `Appeal.process()`.


  `Appeal.process(args=None)`

  Processes a command-line and calls your command functions.
  Stops at the first failure result and returns that result.
  Doesn't catch any errors.  Useful mainly for automation,
  particularly for testing, and as the main driver underlying
  `Appeal.main()`.


  `Appeal.default_options()`

  `Appeal.default_long_option()`

  `Appeal.default_short_option()`

  These functions create the default options for a keyword-only
  parameter.  They're all valid callbacks for the `default_options`
  parameter for the `Appeal()` constructor.  `Appeal.default_options()`
  is the default value for that parameter.

  `Appeal.default_long_option()` creates the option `--{modified_parameter_name}`
  with the default annotation and default value.  `modified_parameter_name` is
  `parameter_name.lower().replace('_', '-')`.

  `Appeal.default_short_option()` creates the option `-{parameter_name[0]}`
  with the default annotation and default value.

  `Appeal.default_options()` creates both.

  In all three cases, if the function isn't able to map at least one option,
  it raises an `AppealConfigurationError`.

  Notes on the default option semantics:

  * When `Appeal.default_option()` converts a keyword-only parameter
    into a long option and a short option, Appeal copies off the first
    character as the short option, and *then* runs a conversion function
    on the string.  The conversion function lowercases the string and
    converts underscores into dashes.  So for the the keyword-only
    parameter `Define`, `Appeal.default_option()`
    would (attempt to) create the two options `-D` and `--define`.
    For the keyword-only parameter `block_type`, it would attempt to
    create `-b` and `--block-type`.

  * What if you have multiple keyword-only parameters that have
    the same first letter?  Only the first mapping succeeds.
    So if you use `def myoptions(*, block_type=None, bad_block=None)`
    as an Appeal command, `-b` will map to `block_type`.  If you
    want it to map to `bad_block`, just swap the two keyword-only
    parameters so `bad_block` is first, or explicitly define your
    options by decorating your function with `Appeal.option()`.
    (As of some recent version, Python guarantees it will maintain
    the order of keyword-only parameters when introspecting a
    function--and it was accidentally true in every version of
    Python before that explicit guarantee anyway.)


  `AppealConfigurationError`

  An exception.
  Raised when the Appeal API is used improperly.

  `AppealUsageError`

  An exception.
  Raised when Appeal processes an invalid command-line.
  Caught by `Appeal.main()`, which uses it to print usage
  information and return an error.

  `AppealCommandError`

  An exception.
  Raised when an Appeal command function returns a
  result indicating an error.  (Equivalent to `SystemExit`.)
  Caught by `Appeal.main()`, which uses it to print usage
  information and return an error.




  The library inspects the parameters of your function and uses
  those for the arguments, options, and opargs of your subcommand:

  * Positional-only and positional-or-keyword parameters
    (parameters before `*,` or `*args,`) map to positional
    arguments.  This:

    ```Python
    @app.command()
    def fgrep(pattern, file, file2=None):
        ...
    ```

    would take two required command-line arguments, "pattern"
    and "file", and an optional third command-line argument "file2".

  * Keyword-only parameters map to options.  They must have a default
    value.  The name of the
    parameter is the name of the option, e.g. this subcommand
    accepts a `--verbose` argument:

    ```Python
    @app.command()
    def foo(*, verbose=False):
        ...
    ```

  * If an argument to your function has an annotation, that
    value is called to convert the string from the command-line
    before passing in to your function.  e.g.

    ```Python
    @app.command()
    def foo(level:int):
        ...
    ```

    would call `int` on the string from the command-line before
    passing it in to level.

  * If a parameter to your function doesn't have an annotation,
    but *does* have a default value, it behaves as if you added
    an annotation of `type(default_value)`.  e.g.

    ```Python
    @app.command()
    def foo(level=0):
        ...
    ```

    would also call `int` on the string from the command-line before
    passing it in to `level`.

    * Keyword-only parameters with a `bool` annotation or a boolean
      default value are special: they don't take an argument.  Instead,
      they toggle the default value.

    * Parameters with a default value of `None` and no annotation
      are also slightly special, in that they take a `str` argument
      (as taking a `NoneType` argument doesn't make sense).

    * Appeal automatically adds single-letter options for keyword-only
      parameters when possible.  Since keyword-only parameters maintain
      their order in Python*++*, Appeal gives the single-letter shortcut to
      the first parameter that starts with that letter.  e.g.

      ```Python
      @app.command()
      def foo(*, verbose=False, varigated=0):
          ...
      ```

      `-v` would map to `--verbose`, not `--varigated`.

  Putting it all together: if you wanted to write an "fgrep" subcommand
  with a "usage" string like this:

      fgrep [-v|--verbose] [--level <int>] pattern [ file1 [ file2 ... ] ]

  you'd write it as follows:

  ```Python
  @app.command()
  def fgrep(pattern, *file, verbose=False, level=0):
      ...
  ```

   *++* This is now guaranteed behavior in current Python, and even
      in the Python 3 series before that, it was always true anyway.




  The POSIX standard defines command-line behavior for all POSIX
  utility commands, in 1003.1, Chapter 12, currently at revision POSIX.1-2017:

    https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html

  Appeal isn't a perfect match for POSIX semantics; it disallows some
  things POSIX allows, and allows some things POSIX disallows.

  * As per required POSIX semantics (1003.1-2017, Chapter 12),
    options can never be required.  It therefore follows that
    in Appeal, keyword arguments to command functions must
    always have a default.
  * The POSIX standard makes no mention of "long options",
    so it's not clear whether or not the standard permits them.
    (Presumably they will be permitted in a future standard.)
  * POSIX requires that options that accept/require multiple opargs
    should accept them as a single string with either spaces
    or commas separating the opargs.  Appeal supports this behavior
    with `appeal.split`.  But it also permits options that consume
    multiple separate opargs from the command-line.
  * POSIX requires that all options be specified before any positional
    arguments.  Appeal doesn't enforce this, and will happily consume
    options and positional arguments in any order.  In fact,
    "subcommands" require permitting options after positional arguments
    for anything beyond the simplest possible subcommand support.
  * POSIX requires that, if an option (short option) has a single
    *optional* argument (oparg), its argument must be concatenated
    to the option.  For example, if `-f` takes an optional argument,
    and you want to specify the argument `guava`, you *must* write
    this as `-fguava`, no other spelling is permissible.  While Appeal
    supports this spelling, it also supports `-f=guava` and `-f guava`.
    More importantly, if you specify `-f` on your command-line (and
    not `-f=<something>` or `-f<something>`), Appeal *will* consume the
    next argument on the command-line as an oparg, which is what POSIX
    definitely does *not* want.  I feel Appeal's consistency is
    more important than supporting this syntactic hack.  Note that
    the oparg is still optional, so if `-f` is the last thing
    on your command-line, that will achieve this "option with
    default value" behavior.




  * You can specify options and arguments in any order on a
    command-line, Appeal doesn't care.  If you want Appeal to
    stop recognizing arguments starting with dashes as options,
    specify `--` (two dashes with nothing else).  All subsequent
    strings on the command-line will be used as arguments, even
    if they start with a `-`.
  * Many built-in types are not introspectable.  If you call
    `inspect.signature(int)` it throws a `ValueError`.  Appeal
    has special-cased exactly five built-in types: `bool`,
    `int`, `str`, `complex`, and `float`.
  * `Accumulator` actually allows parameterizing multiple types,
    separated by commas.  `Accumulator[int, float]` will take
    two opargs each time the option is specified, and the first
    will be an `int` and the second will be a `float`.  The
    list returned will contain tuples of ints and floats.
  * You can't call `main()` on an Appeal object more than once.
    The `Appeal()` instance you use has internal state that changes
    when you execute its `main()` method.
  * Information about a particular converter is localized to
    a particular `Appeal()` instance.  If you decorate a converter
    with `@app.option()`, every place inside that `Appeal()` instance
    that you use that converter will also pick up the changes you
    made with `@app.option()`.
  * You shouldn't call `usage()` until you've added all the
    commands, options, and parameters information into your
    Appeal object.  Why?  Because, for example, `usage()`
    computes the default options for keyword-only parameters
    that haven't gotten any explicitly defined options.
    But if you then define one of those options, Appeal will
    throw an error at you.
  * Almost any callable can be a converter--but not *every*
    function.  There are two limitations.  First, as already
    mentioned, in order for a function to be a legal converter,
    every keyword-only parameter must have a default value.
    The second requirement is more specific: in order to use
    a function as a converter for a `*args*` parameter,
    *somewhere* in the annotations tree under that function,
    some function must take a required positional parameter.

  Finally, the UNIX `make` command has an interesting
  and subtle behavior.  The `--jobs` and `-j` options to `make`
  specify how many jobs to run in parallel.  If you run
  `make` without any parameters, it runs one job at a time.
  If you run `make -j 5`, it runs five jobs at a time.  But!
  If you specify `make -j`, where `-j` is the last thing on the
  command-line it runs *as many jobs at a time as it wants*.
  In a way, the `-j` option has *two default values.*

  Can you do this with Appeal?  Naturally!  Simply specify
  your keyword-only parameter with both an annotation and
  a default value, then design the annotation function
  to take one argument that *also* has a default value.
  Like so:

  ```Python
  def jobs(jobs:int=math.inf):
      return jobs

  @app.command()
  def make(*targets, jobs:jobs=1):
      ...
  ```


  Restrictions on Appeal command functions:

  * You may not use `inspect.Parameter.empty` as a default value
    for any keyword-only parameter to a converter or command function.
  * The converter for a *var_positional* (`*args`) parameter
    *must* require at least one positional argument.




  **next version** *under development*

  **0.6.3** *2024/09/06*

  * Bugfix for usage.  If conversaion fails for a command-line
    parameter, Appeal now prints a context-specific error,
    followed by usage for the command that failed.  Fixes #18.
  * Bugfix for `read_mapping`.  Previously you couldn't have
    two parameters with the same name anywhere in the annotations
    tree for a mapping function, and now you can.

  **0.6.2**  *2023/10/12*

  * Presentation change: if you run a program without arguments,
    runs no-argument `help` instead of `usage`.  This prints out
    both usage information and a list of commands, which seems more
    useful.  That's how most modern programs do it (e.g. `git`, `hg`).
  * Minor API change: renamed Appeal's custom exceptions, to
    remove the word `Appeal`.  So, for example, `AppealUsageError`
    is now simply `UsageError`.  I added aliases so the old names
    still work; I'll remove them eventually, but I promise to
    keep the old names around for at least one year.
  * Fixed usage generation, added tests.
  * Fixed a bug where using a "simple type" (e.g. bool, float)
    as an annotation for an option would cause Appeal help to
    raise an exception.  Fixes #15.

  **0.6.1**  *2023/07/22*

  * Fixed 3.6 and 3.7 support--some equals-sign-in-f-strings
    uses crept in.
  * Added GitHub Actions integration.  Tests and
    coverage are run in the cloud after every checkin.
    Thanks to [Dan Pope](https://github.com/lordmauve)
    for gently walking me through this!
  * Fixed metadata in the `pyproject.toml` file.
  * Added badges for testing and supported Python
    versions.  (Didn't add the coverage badge yet...
    it's too embarassing!)


  **0.6** *2023/07/20*

  A huge upgrade!

  * A new feature: Appeal can now read configuration files!
    Check out the new APIs `Appeal.read_mapping`,
    `Appeal.read_iterable`, and even `Appeal.read_csv`.
    This was a massive undertaking and involved a big
    overhaul of the compiler.

  * The biggest change to existing behavior: Appeal now
    early-maps options.  (See issue #3.)
    In short: when options are only defined in an optional group,
    they get provisionally mapped (made available) *before* the first
    argument in that group.  Using that option enters the group just
    like specifying the first argument in that group.  You'll see
    the difference in usage; an optional group that mapped an option
    used to look like `[a [-v|--verbose] b c]` but now looks like
    `[[-v|--verbose] a b c]`.

  * Appeal now handles multiple short options smashed together
    (e.g. `-ace`) *identically* to them being specified separately
    (e.g. `-a -c -e`).  This caused an observable change in behavior
    regarding when child options get unmapped.

    - Appeal only permits using child options in a limited context:
      it must be after the parent option is executed, it
      must be after the parent option has consumed all its required
      *or optional* opargs, and it must be before any top-level
      positional argument or option mapped before the parent option
      was executed.  But Appeal was lax about enforcing these rules
      when using multiple short options smashed together (e.g. `-ace`);
      it would handle all the options and *then* unmap child options
      as needed.  The good news: Appeal now enforces these rules here
      too.  (The old behavior seems to have been *intentional* on my
      part--what was I *thinking?!)*

  * The usage message raised for an unknown option is now much
    better.  If the option is defined anywhere in the program
    being run, it prints a different message telling you it
    can't be used here, but also tells you where it can be used.
    For example, if you use option `-x`, but that's a child
    option mapped by `--parent`, the message would say
    `-x can't be used here, it must be used immediately after --parent`.

  * Renamed `Appeal.argument` to `Appeal.parameter`.
    This was one of those "what was I *thinking?"* moments.
    The function affects the parameter, not the argument.
    The old name still works but will be removed before 1.0.

  * `short_option_concatenated_oparg` is now more strictly
    enforced: it's only permitted for short options that have
    *exactly one* **optional** oparg, as specified by POSIX.

  **0.5.9**

  * Improved the error message generated when you have a
    required parameter after a `VAR_POSITIONAL` parameter.
    (This command-line can never succeed, because the
    `VAR_POSITIONAL` consumes all remaining arguments on
    the command-line, which means the subsequent required
    parameter can never be satisfied.)  Fixes #6.
  * Changed README to use absolute instead of relative links
    for images, which means images should now render properly
    on the Appeal page at PyPI.  Thanks for the PR, Hugo!
  * Switched a bunch of internal classes to use Python "slots".
    Hopefully a tiny memory and speed optimization.
  * Remove unused / unneeded stuff:
      * unused `partial_replace*` functions
      * unused Charm bytecode instructions `jump_relative` and `load_o_option`
      * unneeded CharmInterpreter register `option` (only used in one error
        message, obviated by instead using the program name directly)
  * Now that Appeal depends on [**big**](https://github.com/larryhastings/big)
    anyway, switch to
    [**big**'s `PushbackIterator`.](https://github.com/larryhastings/big#pushbackiteratoriterablenone).
  * Add peephole optimizer step for jump-to-jump optimization.
    Honestly this was probably unnecessary, as I don't think Appeal's
    compiler can even *generate* code with jumps-to-jumps (yet).

  **0.5.8**

  * Fixed up the "name" of the program for options.  We used to take
    the name of the command, add all the option strings, and join it
    together with commas, as in `'command, -o, --option'`.  Now it
    looks like `'command -o | --option'`.
  * Fixed presentation bug: if you didn't have enough positional
    arguments for your command function, but you ever invoked an
    option on the command-line, the usage text would contain the
    name of the last option invoked (aka the last Charm program run).
    Added a regression test for this.
  * Cleaned up implementation a little: instead of using mystery
    lists on the `CharmInterpreter` stack and context_stack, I now
    use instances of bespoke `CharmStackEntry` and
    `CharmContextStackEntry` classes.

  **0.5.7**

  * Rewrite the technology behind `accumulator[...]` and
    `mapping[...]`.  It previously used `exec()`, which was
    limiting; for example, you couldn't use your own types
    or converters.  The new implementation should be much
    more robust; it now manually defines an explicit signature
    for the `option()` method of the subclass it creates.
  * This fixed a regression, where you couldn't use a locally-defined
    class (e.g. `IntFloat`) as one of the types in the square
    brackets for `accumulator` and `mapping`.  Added a test
    for this.

  **0.5.6**

  * Fix formatting for usage when you have a
    global command *and* subcommands.

  **0.5.5**

  * Add support for `typing.Annotated`, new in Python 3.9.
  * Add dependency to
    [my **big** library.](https://github.com/larryhastings/big)
    This gives Appeal a much better implementation of `multisplit`,
    and I plan to switch to the **big** word wrapper and columnizer
    functions, which are a... "big" improvement over what's in
    Appeal right now.
  * Rename `SingleOption` to just `Option`.  (The name
    `SingleOption` is now deprecated, but I'll leave it
    as a redundant name for `Option`... for now.)

  **0.5.3**

  * Fix compatibility back to Python 3.6.

  **0.5.2**

  * Fix compatibility with Python 3.11.  Python's `inspect.Parameter` object
    no longer allows a `name` that happens to be a keyword, which was a minor
    inconvenience (Appeal used to use `lambda` here sometimes).

  **0.5.1**

  * Fixed regression, issue #5.  If you didn't supply enough required
    parameters, you'd get a `TypeError` instead of a proper usage error.

  **0.5**

  * Initial release!


pkgId: cb1e00478d509dc53f28abcb1958f4758927b1a8bf08668a2b8a88ae010d5ab4
name: python-appel
arch: src
version: 0.3.0
epoch: 0
release: 1
url: https://github.com/zucchini/appel
rpm_license: MIT license
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appel-0.3.0-1.src.rpm
checksum_type: sha256
summary:
  Appel contains tools to process Google Spreadsheets for attendance records and store results on Canvas.


pkgId: eb07a9e8f209724d22e4bb3a5026509e300ebbc4c5005456c58b08f127f84017
name: python-appengine-python-standard
arch: src
version: 1.1.7
epoch: 0
release: 1
summary: Google App Engine services SDK for Python 3
url: https://github.com/GoogleCloudPlatform/appengine-python-standard
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appengine-python-standard-1.1.7-1.src.rpm
checksum_type: sha256
description:
  This is a release of the App Engine services SDK for Python 3.  It provides access to various services and API endpoints that were previously only available on the Python 2.7 runtime.

  See the [documentation](https://cloud.google.com/appengine/docs/standard/python3/services/access) to learn more about using this SDK, and learn more about it in [this product announcement](http://cloud.google.com/blog/products/serverless/support-for-app-engine-services-in-second-generation-runtimes) (Fall 2021).

  Additional examples (Datastore [NDB], Task Queues [push tasks], Memcache) can be found in the [App Engine migration repo](https://github.com/googlecodelabs/migrate-python2-appengine). (Specifically look for samples whose folders have a `b` but where the Python 2 equivalent folder does **not** have an `a`, meaning this SDK is required, e.g., Modules 1 [`mod1` and `mod1b`], 7, 12, etc.)




  In your `requirements.txt` file, add the following:

  `appengine-python-standard>=1.0.0`

  To use a pre-release version (Eg. `1.0.1-rc1`), modify the above line to `appengine-python-standard>=[insert_version]` (Eg. `appengine-python-standard>=1.0.1-rc1`).

  In your app's `app.yaml`, add the following:

  `app_engine_apis: true`

  In your `main.py`, import `google.appengine.api.wrap_wsgi_app()` and call it on your
  WSGI app object.

  Example for a standard WSGI app:

      import google.appengine.api

      def app(environ, start_response):
          start_response('200 OK', [('Content-Type', 'text/plain')])
          yield b'Hello world!\n'

      app = google.appengine.api.wrap_wsgi_app(app)

  Example for a Flask app:

      import google.appengine.api
      from flask import Flask, request

      app = Flask(__name__)
      app.wsgi_app = google.appengine.api.wrap_wsgi_app(app.wsgi_app)

  Then deploy your app as usual, with `gcloud app deploy`. The following modules are available:

  - `google.appengine.api.app_identity`
  - `google.appengine.api.background_thread`
  - `google.appengine.api.blobstore`
  - `google.appengine.api.capabilities`
  - `google.appengine.api.croninfo`
  - `google.appengine.api.dispatchinfo`
  - `google.appengine.api.images`
  - `google.appengine.api.mail`
  - `google.appengine.api.memcache`
  - `google.appengine.api.modules`
  - `google.appengine.api.oauth`
  - `google.appengine.api.runtime`
  - `google.appengine.api.search`
  - `google.appengine.api.taskqueue`
  - `google.appengine.api.urlfetch`
  - `google.appengine.api.users`
  - `google.appengine.ext.blobstore`
  - `google.appengine.ext.db`
  - `google.appengine.ext.gql`
  - `google.appengine.ext.key_range`
  - `google.appengine.ext.ndb`
  - `google.appengine.ext.testbed`




  To install the code from the `main` branch on GitHub rather than the latest
  version published to PyPI, put this in your `requirements.txt` file instead of
  `appengine-python-standard`:

  `https://github.com/GoogleCloudPlatform/appengine-python-standard/archive/main.tar.gz`


pkgId: 4170d12ee672bdac0f810b3b8b145ec38431f556fa9f432e7acfad2796015d74
name: python-appfire-connect-sdk
arch: src
version: 1.9.1
epoch: 0
release: 1
summary: SDK for creating and deploying appfire connect apps (jira, confluence and bitbucket)
url: https://bitbucket.org/appfire/ac-app-installer/src/master/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appfire-connect-sdk-1.9.1-1.src.rpm
checksum_type: sha256
description:
  Creates a Atlassian Connect app template and provides a toolkit for deploying to AWS environment



  ```
  --verbose, -v : "verbose"
  --region, -r : "AWS region", default="us-east-1"
  --profile, -p : "AWS profile as the default environment", default="default"
  --env, -e : "personal, dev, test, stage or prod", default="personal"
  --stack, -s : "CDK stack to deploy", default="app", "core" or "app"
  --stage, -stage : "dev, test, stage or prod", default="dev"
  --app-suffix, -as : "green" or nothing (not passing the argument)
  ```





  ```
  create-appfire-app -v
  ```





  ```
  appfire bootstrap
  ```



  ```
  appfire deploy -s core
  ```



  ```
  appfire deploy -s biz-service
  ```



  ```
  appfire deploy -s app-service
  ```



  ```
  appfire deploy -s module-service
  ```



  ```
  appfire deploy
  ```



  ```
  appfire list (or appfire ls)
  ```



  ```
  appfire diff
  ```



  ```
  appfire destroy
  ```





  ```
  appfire run
  ```



  ```cmd
  python setup.py build && python setup.py install
  ```



  1. If you are getting `SSL: CERTIFICATE_VERIFY_FAILED` while running install command, run following file

  ```cmd
  /Applications/Python 3X/Install Certificates.command
  ```


pkgId: 6fc27c6aec2c510e469750b30d56087312eea6c238355dfc5b3d14245134a276
name: python-appia
arch: src
version: 7.3.2
epoch: 0
release: 1
summary: Chromatography processing made easy
url: https://github.com/PlethoraChutney/Appia
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-appia-7.3.2-1.src.rpm
checksum_type: sha256
description:
  [![DOI](https://zenodo.org/badge/160436725.svg)](https://zenodo.org/badge/latestdoi/160436725)
  ![Tests](https://github.com/plethorachutney/appia/actions/workflows/tests.yaml/badge.svg)


  Appia is a set of scripts to process and view chromatography data from AKTA, Shimadzu, and
  Waters systems. Chromatography data can then be viewed on the easy-to-use and intuitive
  web interface, built with [plotly dash](https://plotly.com/dash/). Please check out the
  [web demo](https://traces.baconguislab.com/)!

  If you find Appia useful in your research, please [cite it](https://dx.doi.org/10.1371/journal.pone.0280255).




  For Mac or PC I've written some wrappers around the manual installation process (below).
  You still need to install [docker](https://www.docker.com/), but then just download
  the appropriate `install-appia-web` script and run it. You'll be prompted to set your
  username and password --- you'll need these to set up your processing installations.



  1. Install [docker](https://www.docker.com/)
  2. Copy `docker-compose.yml` and `local.ini` wherever you want the database to save data
  3. If you're installing Appia Web on an Apple Silicon Mac, set $APPIA_ARCH to "arm64-"
  3. Set the $COUCHDB_USER and $COUCHDB_PASSWORD environment variables (**in your environment!**)
  4. Run `docker-compose up` in the same directory as docker-compose.yml
  5. Open the port from `docker-compose.yml` to allow other instruments to access the web ui
  6. *(Optional) Instead of opening the port, put Appia behind a reverse proxy server with authentication*

  Once you've installed Appia Web, you can access it directly
  by opening ports 8080 (for Appia Web) and 5984 (for the database)
  and accessing it at `{server name}:8080/traces`.
  However, we recommend you host it behind [NGINX](https://www.nginx.com/), both to remove
  the requirement of specifying a port, and to add the ability
  to control access to your Appia database. Here is an example
  config for NGINX:

  ```
  location /traces/ {
      proxy_set_header HOST $http_host;
      proxy_pass http://{server name}:8080/traces/;
  }
  ```

  This way, your users can access the server directly at
  `{server name}/traces`, and you can add a password using
  [the standard methods](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/).
  If using NGINX, you no longer need to open port 8080. You do
  still need to open port 5984 for the database.


  This process will install Python and all the packages/libraries you need.
  I highly recommend you use a virtual environment for the python packages. Conda
  is also fine, but I'm trying to keep this as simple as possible. If you want
  to use ggplot to make manually-tweaked plots, you should also follow the steps
  to install R and RStudio.

  1. Install [python3](https://www.python.org/)
      1. *(Recommended)* Run `python -m virtualenv venv`
      2. *(Recommended)* Run `venv/Scripts/activate` (Windows) or `source venv/bin/activate` (Mac/Linux)
  2. Run `python -m pip install appia` (`python3` for linux)
  3. *(If using Appia Web)* set up database access using `appia utils --database-setup`.

  After performing these steps, Appia is ready to process your data! Detailed information about
  processing supported instruments is given below, but if you want to jump right in, you can
  either use the included batch scripts, or run `appia process {filenames}` to start processing!



  To update Appia Web, open a terminal/powershell window and run the following
  commands in the Appia Web directory:

  ```
  docker-compose pull
  docker-compose down
  docker-compose up -d --remove-orphans
  ```

  This will download the latest docker image and start it up on your server.

  To update the Appia processing scripts, simply run `python3 -m pip install --upgrade appia` in any terminal window.



  Appia can currently process the following HPLC data files:

  | Manufacturer | Expected File Format       |
  | ------------ | -------------------------- |
  | Waters       | `.arw`                     |
  | Shimadzu     | `.asc` (old), `.txt` (new) |
  | Agilent      | `.csv`                     |


  *Please note that our lab uses Waters instruments*. Others are supported, but
  we will need more information from you for non-Waters bug reports and
  feature requests!


  To be able to convert between retention volume and time, Appia needs flow rates.
  You can provide these in a few ways.

  1. (Recommended) Use `appia utils --flow-rate` to add a method name and a flow
  rate. You can add just a part of the method name. For example, `appia utils
  --flow-rate 10_300 0.5` will create a flow rate entry which would match
  methods like `Sup6_10_300_PumpA` and `10_300_FLR-GFP` but not `5_150_ChA`.
  Multiple matches will force you to manually enter, so don't make them
  too broad. These settings are stored at `~/.appia-settings.json`.
  2. Provide a flow rate during processing. This will set *all* HPLC flow rates
  for this processing batch.
  3. Provide flow rates for each file manually. If you did not use one of the
  above methods, Appia will prompt you individually for each file.


  When exporting your data, please export the headers as two rows with multiple columns,
  rather than two columns with multiple rows.

  The Waters script requires `SampleName`, `Channel`, `Instrument Method Name` and
  `Sample Set Name`. The order is not important, so long as the
  required headers are present in the .arw file. Other information can be there as
  well, it won't hurt anything. Flow rate information is pulled from
  `processors/flow_rates.json`. If your Instrument Method contains *exactly one* key
  from that JSON file, the flow rate is set accordingly. If the file does not exist,
  or if your Method matches more or fewer than one key, you will be asked to fill
  provide a flow rate. They can also be provided using the `--hplc-flow-rate`
  argument.



  **Older Instruments**

  If you are using an old Shimadzu instrument, your method will need the
  standard headers, including `Sample ID`, `Total Data Points`, and `Sampling Rate`. When you process, you will need
  to pass a set of arguments to tell Appia which channel corresponds to what,
  since Shimadzu instruments only output a letter. Additionally, you will be prompted
  for a flow rate (or you can provide one with `--hplc-flow-rate`).

  **New Instruments**

  Newer Shimadzu instruments output much more information about samples, which is great.
  Manual input of flow rate is still necessary, and if you have more than one sample
  with the same Sample Name *and* Sample ID being processed at the same time, they will
  conflict. This should not happen unless you're combining samples from different runs
  into a single processing event, which I consider a rare event. If this is essential
  for your workflow please submit an [issue](https://github.com/PlethoraChutney/Appia/issues).



  Unfortunately, Agilent has rather limited support for data export. Versions of OpenLab
  prior to 2.4 lack the ability to export data in a format that Appia can read. However,
  OpenLab 2.4 [introduced the ability to export data as a csv](https://community.agilent.com/technical/software/f/forum/1297/saving-dx-in-csv-excel-format).

  Following those instructions
  should yield a CSV with two unnamed columns, one representing retention time and the other
  signal. Given this lack of information, other data has to be provided by the user. If your
  file includes the pattern `Channel<###>` (where <###> is replaced by exactly
  three digits), Appia will set the channel for that file to the provided number. If your
  file includes the pattern `Flow<##.##>` (where <##.##> is replaced by any number of digits
  and a period followed by any number of digits, e.g., 1.25) Appia will set the flow
  rate for that file to that number, in mL/min. Otherwise, the user will be prompted for this
  information at the command line.

  We do not have access to an Agilent instrument, and we welcome collaboration on this front!


  Currently, only GE/Cytiva AKTA preparative instruments are supported.
  If you have a different manufacturer, or if your AKTA files do not work
  with Appia, **please** open an issue so we can add more support!

  The AKTA processing is straightforward. First, export your data from the AKTA in
  .csv format. You'll have to open the trace in Unicorn and use the export button there,
  just using "Export Data" saves a zipped binary which Appia can't read. Everything is
  handled automatically, but there are several arguments for producing and customizing
  automatic plots, if desired.



  When you
  process HPLC and/or FPLC data with Appia, you create an Experiment. These Experiments
  are then uploaded to a CouchDB server. The Appia web server pulls data from the
  CouchDB to display traces using plotly dash. This is the main power of Appia --- you
  can use your browser to quickly see your data, zoom in and out, select different
  traces, combine experiments to compare different runs, re-normalize the data, and
  share links with lab members.


  To upload an experiment, when you process it include the `-d` flag. This will
  attempt to read the environment variables `$COUCHDB_USER`, `$COUCHDB_PASSWORD`,
  and `$COUCHDB_HOST` and use those to upload the Experiment to the correct database.
  You can also pass a JSON file to `-d` instead (but you should never save passwords
  in plaintext).


  Simply navigate to your server and view the trace page. The docker default is
  `{myserver}:8080/traces`. You can search
  experiments in the dropdown menu and concatenate HPLC results to compare across
  experiments. Clicking "Renormalize HPLC" will re-normalize the traces to set the
  maximum of the currently-viewed unnormalized region to 1, allowing you to compare
  specific peaks.


  From the command line, the best way to use Appia is to run appia.py. However,
  several batch scripts are included in this repo to give users who prefer not
  to use command line interfaces a set of commonly-used options. You could write
  equivalent shell scripts for Linux or Mac machines, but since most chromatography
  systems run on Windows I've included these for those machines.


  Read all files in the current directory and process all CSV, ASC, and ARW files
  into a new experiment which is uploaded to the database using environment variables


  Same as above, but specify an Experiment ID yourself instead of reading one from
  the data.


  For final publication plots, we typically fine-tune the appearance of the plot
  using [ggplot2](https://ggplot2.tidyverse.org/). To this end, we include some
  R scripts as suggested starting points for building publication plots. These manual
  plotting scripts can be copied into the processed data directory using the
  `--copy-manual` argument during processing. As you develop your own style, you
  can save your own templates (still named `manual_plot_HPLC.R` and `manual_plot_FPLC.R`)
  and pass the directory containing these templates to the `--copy-manual` argument.
  You can, of course, use any plotting software you wish since the data is output
  in both wide and long format.


  Examples of correctly-formatted Waters, Shimadzu, and AKTA files can be found in `/test-files/`. The directory `/processed-tests/` is the result of the command:

  ```python appia.py -v process test-files/*.arw .\test-files\2018_0821SEC_detergentENaC.csv -kpo processed-tests -m 5 20 -f 16 28 2```

  I included the -k parameter because I want to keep the raw files there, but if I
  had not, they'd be moved to their own respective directories in
  `/processed-tests/`. You'll see that in `/processed-tests/` there are three
  files representing the compiled data, as well as auto-generated plots.



  ![Auto HPLC plot](processed-tests/Exp106_SEC_auto-plot-hplc.png)

  For ease of use, HPLC data is stored in both a long and wide format.


  mL is calculated from Time during processing. Sample and Channel are self-explanatory.
  Normalization tells if Value is the raw signal or a normalized Signal from 0 to 1,
  0 being the minimum and 1 being the maximum over that sample/channel combination,
  unless a specific range over which to normalize was passed into Appia during processing.

  | mL | Sample   | Channel | Time | Normalization | Value |
  |----|----------|---------|------|---------------|-------|
  | 0  | 05_25_BB | GFP     | 0    | Signal        | -1    |
  | 0  | 05_25_BB | Trp     | 0    | Signal        | -35   |
  | 0  | 05_25_D  | GFP     | 0    | Signal        | 3     |
  | 0  | 05_25_D  | Trp     | 0    | Signal        | 0     |


  Wide format is the same data, but presented in a more traditional, "excel-style" format.
  Each column represents a trace, with a single column for Time to go along with it. You
  may note that the example wide table has a strange format, with many empty rows. This is
  because Shimadzu and Waters sample at different rates, meaning they do not have overlapping
  sampling points for the most part. Appia handles this, by using a single Time column
  and introducing empty rows in the Signal columns. Your plotting software should be able
  to deal with that, or you can just filter for non-empty rows.

  | Time     | 05_25_BB GFP | 05_25_BB Trp | 05_25_D GFP | 05_25_D Trp |
  |----------|--------------|--------------|-------------|-------------|
  | 0        | -1           | -35          | 3           | 0           |
  | 0.033333 | -1           | -20          | 0           | -1          |



  ![Auto FPLC plot](processed-tests/Exp106_SEC_auto-plot-fplc.png)

  FPLC data is only stored in long format since, by and large, it is the same as
  what wide format would be. You just need to filter out channels you don't care about
  to reproduce what a wide-format table would be. Interestingly, AKTAs sample each channel
  at different rates, meaning that each channel has different x-axis values. This is all
  handled correctly by Appia, but that would introduce blank rows in the wide table, as
  with the HPLC example data. The fraction column indicates the vial into which that
  data point was dumped. This is used to fill fractions of interest, as seen in the
  example FPLC plot and the web interface.

  | mL       | CV       | Channel | Fraction | Sample                     | Normalization | Value    |
  |----------|----------|---------|----------|----------------------------|---------------|----------|
  | -0.00701 | -0.00029 | mAU     | 1        | 2018_0821SEC_detergentENaC | Signal        | 0.031309 |
  | -0.00618 | -0.00026 | mAU     | 1        | 2018_0821SEC_detergentENaC | Signal        | 0.022083 |
  | -0.00535 | -0.00022 | mAU     | 1        | 2018_0821SEC_detergentENaC | Signal        | 0.022115 |


