diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..98ed419 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,407 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. This option is deprecated +# and it will be removed in Pylint 2.0. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=pointless-string-statement,invalid-name,blacklisted-name,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,cyclic-import,missing-docstring,empty-docstring,unneeded-not,no-absolute-import,old-division,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,bad-mcs-classmethod-argument,consider-using-enumerate,consider-iterating-dictionary,bad-classmethod-argument,bad-mcs-method-argument,suppressed-message,useless-suppression,too-many-statements,too-many-locals,too-many-boolean-expressions,too-many-return-statements,too-many-arguments,too-many-branches,ungrouped-imports,duplicate-code,old-style-class,too-many-public-methods,too-many-instance-attributes,too-few-public-methods,too-many-ancestors,redefined-variable-type,no-self-use,no-staticmethod-decorator,no-classmethod-decorator,trailing-newlines,missing-final-newline,line-too-long,trailing-whitespace,too-many-lines,using-cmp-argument,wrong-import-order,multiple-imports,wrong-import-position,unexpected-line-ending-format,import-star-module-level,old-octal-literal,unidiomatic-typecheck,misplaced-comparison-constant,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,oct-method,reload-builtin,nonzero-method,hex-method,multiple-statements,mixed-line-endings,bad-whitespace,superfluous-parens,wrong-spelling-in-docstring,invalid-characters-in-docstring,singleton-comparison,wrong-spelling-in-comment,bad-continuation,input-builtin,round-builtin,cmp-method,map-builtin-not-iterating,zip-builtin-not-iterating,intern-builtin,unichr-builtin,range-builtin-not-iterating,filter-builtin-not-iterating,simplifiable-if-statement,too-many-nested-blocks + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". This option is deprecated +# and it will be removed in Pylint 2.0. +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.travis.yml b/.travis.yml index 0527baf..80c1a0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: python python: - - "3.6" -# need to refactor to requirements/ setup +- '3.6' install: - - pip install pipenv - - pipenv install --dev +- pip install pipenv +- pipenv install --dev script: -# have pylint only show warnings and errors -# and ignore regular syntax errors - - pylint --disable=R,C nessie - - python -m unittest +- pylint --disable=R,C nessie +- python -m unittest +env: + global: + secure: kxSk6tcEhkvodjtDWCeJf/qI/vpRptfGZ6UmZ2G9GbHizXIVFleEmDmIZyWNnqRqv/ugdomlQhif0FRGod/e73MoIV9qa7kGWxuhExOhWzTo8tz0NzyyqLAFTmw5IB8m09H6MhJY9VpPygbpN0aWjOrrKdxRsj0ocVXPn3M62je6Om2ndSjMbynWrYutrhtijcp3BVka2vvjy6iNt9i0ZK/M0dsqqnuEgXe5uL4xKcmpvWu56QYcRhNky6qzxhh3Y1pyoBF7fA7fH2EnECCpnJaBFrdQFJzJ7VNAdoztX15G1E5KSudJu+qnKEw91RoQz9F9lmJgJJlJnYYMrjAqIE1h6eS560vFU1UGUZs1MU+FAWmRS1V09l+YKEtfmXg6NOO8tyzMuOEj1opwA95q1j17Ffg7E3SEs38ICpQYuK3LZUTlXELVsfPizR+70CQFDzWjI/OTIo6xi1gE1gWYUo3bHXIAn5jCJQiWNxMkaKavE+YXj/zWePJIeWlWbx1ZV0kZrhJ+V7Prlh86VYFPyI/jPdj+6Jc8IbvxDtsvCn2WZiHNJqtBTlcTT+kN5WShbq5bC+fMa++uffVe1nFy/cFEMaUX+tN2r/puzSPn4YCZ8BAHOuSKbUMEVk6j8ZKDBzqCMPxoYwFHdrrlbFC+lqEj38Z3+odV9Ni4CrquiUA= diff --git a/README.md b/README.md index c063047..e74a7b7 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Install python 3.6.2 or greater Setup virtual python environment pip install pipenv - pipenv install + pipenv install --dev To add packages @@ -50,6 +50,11 @@ More info about pipenv: http://python-docs.readthedocs.io/en/latest/dev/virtuale To run test package pipenv run python ./src/atmRequests.py + +To test locally + + python3 -m unittest + pylint --disable=R,C nessie ## Deploying to pip diff --git a/docs/overview.md b/docs/overview.md index 5fbc1d2..816264b 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -16,6 +16,7 @@ Customer 1 -- * Accounts ## Transactions +#### Bill #### Desposit #### Loan #### Purchase diff --git a/nessie/atmRequests.py b/nessie/atmRequests.py index d37a5c6..3729d46 100644 --- a/nessie/atmRequests.py +++ b/nessie/atmRequests.py @@ -39,7 +39,7 @@ def __validateParams(self, req): if (paramsInvalid): raise ATMValidationError(constants.atmInvalidFields) - def getAtms(self, lat=None, lng=None, rad=None): + def get_atms(self, lat=None, lng=None, rad=None): reqUrl = "%s/atms" % self.baseUrl par = self.__buildParams(lat, lng, rad) self.__validateParams(par) @@ -50,6 +50,7 @@ def getAtms(self, lat=None, lng=None, rad=None): jsonAtms = r.json() while ('next' in r.json()['paging']): + print(r.json()) reqUrl = "%s%s" % (self.baseUrl, r.json()['paging']['next']) r = requests.get(reqUrl) @@ -60,7 +61,7 @@ def getAtms(self, lat=None, lng=None, rad=None): return self.__formatResponse(jsonAtms) - def getAtmById(self, idCode): + def get_atm(self, idCode): reqUrl = "%s/atms/%s" % (self.baseUrl, idCode) par = {'key': self.key} diff --git a/nessie/billRequests.py b/nessie/billRequests.py index ef53861..244b7ff 100644 --- a/nessie/billRequests.py +++ b/nessie/billRequests.py @@ -76,4 +76,4 @@ def delete_bill(self, bill_id): response = requests.delete(url) if (response.status_code != 200): raise NessieApiError(response) - return response.json() \ No newline at end of file + return response.json() diff --git a/nessie/client.py b/nessie/client.py new file mode 100644 index 0000000..cca7c75 --- /dev/null +++ b/nessie/client.py @@ -0,0 +1,28 @@ +import os + +from .accountRequests import AccountRequests +from .atmRequests import ATMRequest +from .billRequests import BillRequest +from .branchRequests import BranchRequest +from .customerRequests import CustomerRequests +from .dataRequests import DataRequests + +class Client(): + def __init__(self, nessie_api_key=None): + # if no key is set then fetch from environment + if nessie_api_key is None: + try: + self.key = os.environ['NESSIE_API_KEY'] + except KeyError as e: + raise Exception(e,"probably need to `export NESSIE_API_KEY=xxxxxxxxxxx`") + else: + self.key = nessie_api_key + + self.account = AccountRequests(self.key) + self.atm = ATMRequest(self.key) + self.bill = BillRequest(self.key) + self.branch = BranchRequest(self.key) + self.data = DataRequests(self.key) + self.customer = CustomerRequests(self.key) + + \ No newline at end of file diff --git a/nessie/customerRequests.py b/nessie/customerRequests.py index 94c6f7c..28f928e 100644 --- a/nessie/customerRequests.py +++ b/nessie/customerRequests.py @@ -2,9 +2,10 @@ import json import re -from nessie.models.customer import Customer -from nessie.utils.exceptions import CustomerValidationError, NessieApiError, AddressValidationError -from nessie import utils + +from .models.customer import Customer +from .utils.exceptions import CustomerValidationError, NessieApiError, AddressValidationError +from . import utils class CustomerRequests: @@ -56,7 +57,6 @@ def get_customer_by_id(self, customer_id): def create_customer(self, first_name: str, last_name: str, address): if first_name is None or last_name is None: raise CustomerValidationError(utils.constants.createCustomerMissingFields) - val_address = validate_address(address) if val_address != utils.constants.success: raise AddressValidationError(val_address) @@ -66,7 +66,7 @@ def create_customer(self, first_name: str, last_name: str, address): body = { "first_name": first_name, "last_name": last_name, - "address": address.to_dict() + "address": address } r = requests.post(utils.constants.customersUrl, headers=header, params=payload, data=json.dumps(body)) if r.status_code != 201: @@ -80,17 +80,21 @@ def create_customer(self, first_name: str, last_name: str, address): return created_customer # Updates a customer's address based on CustomerId + # weird can only change address not first/last name def update_customer(self, customer_id, new_address): if customer_id is None: raise CustomerValidationError(utils.constants.customerIdMissingField) - + + # needs check because otherwise + # if you set new city (or any one field of address) + # but not the rest it sets the other address fields to none val_address = validate_address(new_address) if val_address != utils.constants.success: raise AddressValidationError(val_address) header = {"Content-Type": "application/json"} payload = {"key": self.key} - body = {"address": new_address.to_dict()} + body = {'address':new_address} url = utils.constants.customersIdUrl % customer_id r = requests.put(url, headers=header, params=payload, data=json.dumps(body)) if r.status_code != 202: @@ -102,6 +106,6 @@ def update_customer(self, customer_id, new_address): def validate_address(address): if address is None: return utils.constants.addressMissingField - elif re.fullmatch(r"^[0-9]{5}$", address.zipcode) is None: + elif re.fullmatch(r"^[0-9]{5}$", address['zip']) is None: return utils.constants.addressValidationZipCode return utils.constants.success diff --git a/nessie/dataRequests.py b/nessie/dataRequests.py index 88f0aa2..493048c 100644 --- a/nessie/dataRequests.py +++ b/nessie/dataRequests.py @@ -13,11 +13,23 @@ def __init__(self,api_key): def delete_data(self, dataType:str): url=f'{baseUrl}/data?type={dataType}&key={self.key}' - response = requests.delete(url) + # 'Connection':'close', + headers = { 'Content-type': 'application/json'} + response = requests.delete(url, headers=headers) # status_code 200 denotes success # status_code 404 denotes success, but no data to delete - if((response.status_code != 200) and (response.status_code!= 404)): + if( + (response.status_code != 200) and + (response.status_code != 204) and + (response.status_code != 404)): raise NessieApiError(response) + + # sometimes the response encoding is nothing + # rather than utf-8 so the response.json() + # will crash + if (response.encoding is None): + return {} + result = response.json() return result diff --git a/nessie/models/address.py b/nessie/models/address.py index 3e8b90f..d3fa8b7 100644 --- a/nessie/models/address.py +++ b/nessie/models/address.py @@ -14,3 +14,6 @@ def to_dict(self): "state": self.state, "zip": self.zipcode } + + def __eq__(self, other): + return self.to_dict() == other.to_dict() diff --git a/nessie/models/atm.py b/nessie/models/atm.py index be22b76..a1fef78 100644 --- a/nessie/models/atm.py +++ b/nessie/models/atm.py @@ -10,7 +10,7 @@ def __init__(self, jsonData): self.hours = jsonData.get('hours') self.geocode = jsonData.get('geocode') - def toDict(self): + def to_dict(self): returnDict = {} returnDict['_id'] = self.atmId returnDict['name'] = self.name diff --git a/nessie/models/bill.py b/nessie/models/bill.py index 6270bc5..0988a87 100644 --- a/nessie/models/bill.py +++ b/nessie/models/bill.py @@ -1,8 +1,6 @@ class Bill(): def __init__(self, _json): - print(_json) - self.bill_id = _json['_id'] self.status = _json["status"] self.payee = _json["payee"] diff --git a/nessie/models/customer.py b/nessie/models/customer.py index 854dd55..827e516 100644 --- a/nessie/models/customer.py +++ b/nessie/models/customer.py @@ -26,3 +26,6 @@ def to_dict(self): "last_name": self.last_name, "address": self.address.to_dict() } + + def __eq__(self, other): + return self.to_dict() == other.to_dict() diff --git a/nessie/models/transaction.py b/nessie/models/transaction.py new file mode 100644 index 0000000..e0c7386 --- /dev/null +++ b/nessie/models/transaction.py @@ -0,0 +1,7 @@ +# underlying base class +# should not be directly used + + +class Transaction(): + def __init__(self, _json): + pass \ No newline at end of file diff --git a/nessie/models/transfer.py b/nessie/models/transfer.py new file mode 100644 index 0000000..2e4bf69 --- /dev/null +++ b/nessie/models/transfer.py @@ -0,0 +1,26 @@ + +class Transfer(): + def __init__(self, _json): + self.transfer_id = _json['_id'] + self.type = _json['type'] + self.transaction_date = _json['transaction_date'] + self.status = ['status'] + self.medium = _json['medium'] + self.payer_id = _json['payer_id'] + self.payee_id = _json['payee_id'] + self.description = _json['description'] + + def to_dict(self): + return vars(self) + + +# """ +# "_id": "string", +# "type": "p2p", +# "transaction_date": "2018-02-01", +# "status": "pending", +# "medium": "balance", +# "payer_id": "string", +# "payee_id": "string", +# "description": "string" +# """ \ No newline at end of file diff --git a/nessie/transactionRequests.py b/nessie/transactionRequests.py new file mode 100644 index 0000000..ea67813 --- /dev/null +++ b/nessie/transactionRequests.py @@ -0,0 +1,69 @@ +# base class transaction +# should not be directly used +import requests +from abc import ABC +from nessie import utils + +""" + Where transaction is a: + bill + deposit + loan + purchase + transfer + withdrawal + + accounts//s + GET fetch all transaction + POST create new transaction under + + transaction/ + GET fetch selected transaction + PUT update selected transaction + DELETE delete selected transaction +""" + +class TransactionRequest(ABC): + def __init__(self, api_key, transaction_name:str, transaction_class): + self.key = api_key + self.base_url = utils.constants.baseUrl + self.transaction = transaction_name + self.transaction_class = transaction_class + + # creates under the provided account + def _create_transaction(self, account_id): + url = f'{self.base_url}/{account_id}/{self.transaction}?key={self.key}' + response = requests.post(url) + result = response.json() + return result + + # return list of python objects from account + def _get_account_transactions(self, account_id): + url = f'{self.base_url}/accounts/{account_id}/{self.transaction}?key={self.key}' + response = requests.get(url) + result = response.json() + + return result + # need to do work here to convert json into objects + + def _get_transaction(self, transaction_id): + url = f'{self.base_url}/{self.transaction}/{transaction_id}?key={self.key}' + response = requests.get(url) + result = response.json() + return result + + def _update_transaction(self, transaction_id): + url = f'{self.base_url}/{self.transaction}/{transaction_id}?key={self.key}' + response = requests.put(url) + result = response.json() + return result + + def _delete_transaction(self, transaction_id): + url = f'{self.base_url}/{self.transaction}/{transaction_id}?key={self.key}' + response = requests.delete(url) + result = response.json() + return result + + + + \ No newline at end of file diff --git a/nessie/transferRequest.py b/nessie/transferRequest.py new file mode 100644 index 0000000..05634b7 --- /dev/null +++ b/nessie/transferRequest.py @@ -0,0 +1,34 @@ +import requests + +from .transactionRequests import TransactionRequest +from .models.transfer import Transfer +from .utils.constants import baseUrl + +class TransferRequests(): + def __init__(self, api_key): + self.key = key + + # a GET request for transfer with + # corresponding transfer_id + def get_transfer(self, transfer_id): + url = f'{baseUrl}/transfers/{transfer_id}?key={self.key}' + response = requests.get(url) + if (response.status_code != 200): + raise NessieApiError(response) + + # GET request to fetch all transfer transactions + # from the specified account + def get_transfers_of_account(self, account_id): + url = f'{baseUrl}/accounts/{account_id}/transfers?key={self.key}' + + def create_transfer(self,medium,payee_id,transaction_date, status,description): + url = f'{baseUrl}/accounts/{account_id}/transfers?key={self.key}' + + body = { + 'medium': medium, + 'payee_id': payee_id, + 'transaction_date': transaction_date, + 'status': status, + 'description':description + } + response = requests.post(url, json=body) \ No newline at end of file diff --git a/tests/test_atm_requests.py b/tests/test_atm_requests.py new file mode 100644 index 0000000..5e07219 --- /dev/null +++ b/tests/test_atm_requests.py @@ -0,0 +1,109 @@ +import unittest +import sys, os +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0,path+'/../') + +from nessie.client import Client + +class TestAtmRequests(unittest.TestCase): + + def setUp(self): + # implicitly get NESSIE_API_KEY from env + self.client = Client() + + # no tearDown needed as its + # read only functions + # def tearDown(self): + # pass + + + # test_get_atms go intos an infinite loop + # def test_get_atms(self): + # lat = 38.9283 + # lng = -77.1753 + # rad = 1 + + # observed_atms = self.client.atm.get_atms(lat,lng,rad) + # print(observed_atms) + # expected_firsttwo_atms = [ + # { + # "_id": "56c66be5a73e492741506f4b", + # "name": "McLean 1", + # "geocode": { + # "lng": -77.17829449999999, + # "lat": 38.932887 + # }, + # "accessibility": 'true', + # "hours": [ + # "24 hours a day, 7 days a week" + # ], + # "address": { + # "state": "VA", + # "zip": "22101", + # "city": "McLean", + # "street_name": "Chain Bridge Road", + # "street_number": "1439" + # }, + # "language_list": [ + # "English" + # ], + # "amount_left": 273775 + # }, + # { + # "_id": "56c66be5a73e492741506f4c", + # "name": "McLean 2", + # "geocode": { + # "lng": -77.17829449999999, + # "lat": 38.932887 + # }, + # "accessibility": 'false', + # "hours": [ + # "24 hours a day, 7 days a week" + # ], + # "address": { + # "state": "VA", + # "zip": "22101", + # "city": "McLean", + # "street_name": "Chain Bridge Road", + # "street_number": "1439" + # }, + # "language_list": [ + # "Portuguese", + # "Korean", + # "Spanish", + # "Chinese", + # "French", + # "English" + # ], + # "amount_left": 444425 + # }] + + # self.assertEqual(observed_atms,expected_atms) + + def test_get_atm(self): + atm = self.client.atm.get_atm('56c66be5a73e492741506f4b') + observed_atm = atm.to_dict() + expected_atm = { + "_id": "56c66be5a73e492741506f4b", + "name": "McLean 1", + "geocode": { + "lng": -77.17829449999999, + "lat": 38.932887 + }, + "accessibility": True, + "hours": [ + "24 hours a day, 7 days a week" + ], + "address": { + "state": "VA", + "zip": "22101", + "city": "McLean", + "street_name": "Chain Bridge Road", + "street_number": "1439" + }, + "language_list": [ + "English" + ], + "amount_left": 273775 + } + self.assertEqual(observed_atm, expected_atm) \ No newline at end of file diff --git a/tests/test_bill_requests.py b/tests/test_bill_requests.py index 913c06b..b1ddb8b 100644 --- a/tests/test_bill_requests.py +++ b/tests/test_bill_requests.py @@ -4,26 +4,24 @@ sys.path.insert(0,path+'/../') from nessie.accountRequests import AccountRequests -from nessie import billRequests +from nessie.billRequests import BillRequest from nessie.dataRequests import DataRequests +from nessie.client import Client from nessie.utils.exceptions import NessieApiError + # def test_create_bill(self): # bill_factory = billRequests.BillRequest("wkey") -wkey = '7e9b72fdb7b286fcd0aae87deb0e09a2' - -# customer_id = '5a546ffd6514d52c7774a2ca' -# account_factory = AccountRequests(wkey) -# account_factory.createCustomerAccount(customer_id) -# premade account id -account_id = '5a5471796514d52c7774a2cb' -class TestBillRequests(unittest.TestCase): +class TestBillRequests(): # create some dummy bills def setUp(self): - bill_factory = billRequests.BillRequest(wkey) + # implicitly get NESSIE_API_KEY from env + self.client = Client() + + bill_factory = self.client.bill bill = bill_factory.create_bill(account_id, status='pending', payee='bobby', @@ -34,14 +32,27 @@ def setUp(self): ) def tearDown(self): - data_deletor = DataRequests(wkey) + data_deletor = self.client.data + response = data_deletor.delete_data('Bills') + d = DataRequests('7e9b72fdb7b286fcd0aae87deb0e09a2') + d.delete_data('Bills') + + + def test_get_bill_succeed(self): + print("test_get_bill_succeed") + bill_factory = self.client.bill + result = bill_factory.get_account_bills(account_id) + print(result) + # result = bill_factory.get_bill("5a261c3883a71c405074fcbd") + # expected_result = {'bill_id': '5a261c3883a71c405074fcbd', 'status': 'pending', 'payee': 'string', 'nickname': 'string', 'payment_date': '2017-12-05', 'recurring_date': 1, 'payment_amount': 23, 'creation_date': '2017-12-05', 'account_id': '5a261a0483a71c405074fcbc'} + self.assertEqual(result[0]['payment_date'], '2018-01-10') + data_deletor = DataRequests(wkey) # try fetching a bill that doesn't exist def test_get_nonreal_bill_fail(self): print("test_get_nonreal_bill_fail") - bill_factory = billRequests.BillRequest(wkey) - + bill_factory = self.client.bill expected_status_code = 404 # expected_message = 'Invalid ID' @@ -53,7 +64,4 @@ def test_get_nonreal_bill_fail(self): self.assertEqual(result.code,expected_status_code) - - - \ No newline at end of file diff --git a/tests/test_branch_requests.py b/tests/test_branch_requests.py new file mode 100644 index 0000000..f0ea565 --- /dev/null +++ b/tests/test_branch_requests.py @@ -0,0 +1,62 @@ +import unittest +import sys, os +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0,path+'/../') + +from nessie.client import Client + +class TestBranchRequests(unittest.TestCase): + + def setUp(self): + # implicitly get NESSIE_API_KEY from env + self.client = Client() + + # no tearDown needed as its + # read only functions + # def tearDown(self): + # pass + + def test_get_branch_succeed(self): + branch = self.client.branch.get_branch_by_id('56c66be5a73e4927415071a3') + observed_branch = branch.to_dict() + expected_branch = { + "_id": "56c66be5a73e4927415071a3", + "name": "ARLINGTON", + "phone_number": "(703) 812-8550", + "hours": [ + "Sun", + "Mon 9 AM - 5 PM", + "Tue 9 AM - 5 PM", + "Wed 9 AM - 5 PM", + "Thu 9 AM - 5 PM", + "Fri 9 AM - 6 PM", + "Sat 9 AM - 1 PM" + ], + "notes": [ + "Safe Deposit Box", + "Branch Drive-Up", + "ATM Available", + "Open on Saturday" + ], + "address": { + "street_number": "4700", + "state": "VA", + "street_name": "Lee Highway", + "zip": "22207", + "city": "Arlington" + }, + "geocode": { + "lng": -77.1211338, + "lat": 38.8981779 + } + } + self.assertEqual(observed_branch, expected_branch) + + def test_get_branch_fail(self): + pass + + def test_get_branches_succeed(self): + pass + + def test_get_branches_fail(self): + pass \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..5901d33 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,15 @@ +import unittest +import sys, os +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0,path+'/../') + +from nessie.client import Client + +class TestClient(unittest.TestCase): + + def test_client_initialize(self): + # should implicitly retrieve key from + # env.NESSIE_API_KEY + client = Client() + + self.assertIsNotNone(client.key) \ No newline at end of file diff --git a/tests/test_customer_requests.py b/tests/test_customer_requests.py new file mode 100644 index 0000000..5662243 --- /dev/null +++ b/tests/test_customer_requests.py @@ -0,0 +1,127 @@ +import unittest +import sys, os +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0,path+'/../') + +from nessie.client import Client +from nessie.customerRequests import CustomerRequests +from nessie.models.customer import Customer + +class TestCustomerRequests(unittest.TestCase): + + def setUp(self): + # implicitly get NESSIE_API_KEY from env + self.client = Client() + + self.client.data.delete_data('Customers') + + self.client.customer.create_customer( + first_name = "Adam", + last_name = "Smith", + address = { + "street_number": "1", + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + ) + + def tearDown(self): + self.client.data.delete_data('Customers') + + def test_get_customer(self): + customers = self.client.customer.get_all_customers() + + # assert there is one customer + self.assertEqual(len(customers), 1) + self.assertEqual(type(customers[0]), Customer) + + def test_create_and_customer(self): + + customer_json = { + "first_name":"Adam", + "last_name":"Smith", + "address": { + "street_number": "1", + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + } + + # generated customer from json + expected_customer = Customer(customer_json) + + new_customer = self.client.customer.create_customer( + first_name = "Adam", + last_name = "Smith", + address = { + "street_number": "1", + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + ) + + # newly created customer id + new_customer_id = new_customer.customer_id + + # the actual customer created and returned + actual_customer = self.client.customer.get_customer_by_id(new_customer_id) + + # add customer_id on (its impossible for + # the expected to know the dynamic id) + expected_customer.customer_id = actual_customer.customer_id + + # print("expected_customer:") + # print(vars(expected_customer)) + # print("actual_customer:") + # print(vars(actual_customer)) + # print("id",expected_customer.customer_id) + self.assertEqual(actual_customer,expected_customer) + + + # create customer bill + # update customer bill + # get customer bill and verify + # delete customer bill + def test_create_update_get_customer(self): + new_customer = self.client.customer.create_customer( + first_name = "Bob", + last_name = "Anderson", + address = { + "street_number": "2", + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + ) + + # try updating + + customer_id = new_customer.customer_id + + self.client.customer.update_customer( + customer_id, + { + 'street_number': '3', + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + ) + + customer = self.client.customer.get_customer_by_id(customer_id) + expected_address = { + "street_number": "3", + "street_name": "Dolly Madison Blvd", + "city": "Tysons Corner", + "state": "VA", + "zip": "12345" + } + self.assertEqual(customer.address.to_dict(),expected_address) \ No newline at end of file diff --git a/tests/test_transfer_requests.py b/tests/test_transfer_requests.py new file mode 100644 index 0000000..b1ddb8b --- /dev/null +++ b/tests/test_transfer_requests.py @@ -0,0 +1,67 @@ +import unittest +import sys, os +path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0,path+'/../') + +from nessie.accountRequests import AccountRequests +from nessie.billRequests import BillRequest +from nessie.dataRequests import DataRequests +from nessie.client import Client +from nessie.utils.exceptions import NessieApiError + + +# def test_create_bill(self): +# bill_factory = billRequests.BillRequest("wkey") + + + +class TestBillRequests(): + # create some dummy bills + def setUp(self): + # implicitly get NESSIE_API_KEY from env + self.client = Client() + + bill_factory = self.client.bill + bill = bill_factory.create_bill(account_id, + status='pending', + payee='bobby', + nickname='bob', + payment_date='2018-01-10', + recurring_date=1, + payment_amount=10 + ) + + def tearDown(self): + data_deletor = self.client.data + response = data_deletor.delete_data('Bills') + d = DataRequests('7e9b72fdb7b286fcd0aae87deb0e09a2') + d.delete_data('Bills') + + + + def test_get_bill_succeed(self): + print("test_get_bill_succeed") + bill_factory = self.client.bill + result = bill_factory.get_account_bills(account_id) + print(result) + # result = bill_factory.get_bill("5a261c3883a71c405074fcbd") + # expected_result = {'bill_id': '5a261c3883a71c405074fcbd', 'status': 'pending', 'payee': 'string', 'nickname': 'string', 'payment_date': '2017-12-05', 'recurring_date': 1, 'payment_amount': 23, 'creation_date': '2017-12-05', 'account_id': '5a261a0483a71c405074fcbc'} + self.assertEqual(result[0]['payment_date'], '2018-01-10') + data_deletor = DataRequests(wkey) + + # try fetching a bill that doesn't exist + def test_get_nonreal_bill_fail(self): + print("test_get_nonreal_bill_fail") + bill_factory = self.client.bill + + expected_status_code = 404 + # expected_message = 'Invalid ID' + result = {} + try: + try_result = bill_factory.get_bill("fake") + except NessieApiError as e: + result = e + + self.assertEqual(result.code,expected_status_code) + + \ No newline at end of file