Skip to content

Commit

Permalink
3.1.4 New Version Release (#20)
Browse files Browse the repository at this point in the history
* 3.1.4 Code Commit

* Testing Update

* More Test Fixes

* Update nationstates.rst

* More Test Additions

* testing fixes
  • Loading branch information
DolphDev committed Sep 23, 2021
1 parent 4d64275 commit d263b92
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 71 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Expand Up @@ -57,9 +57,9 @@
# built documents.
#
# The short X.Y version.
version = '3.0.3'
version = '3.1'
# The full version, including alpha/beta/rc tags.
release = '3.0.3.9'
release = '3.1.4'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 3 additions & 1 deletion docs/pages/nationstates.rst
Expand Up @@ -84,7 +84,9 @@ You can view what each endpoint currently supports in it's magic methods by view
>>> nationstates.get_shard
{'get_customreligion', 'get_rcensus', 'get_banners', 'get_govtpriority', 'get_banner', 'get_census', 'get_gavote', 'get_wcensus', 'get_firstlogin', 'get_notable', 'get_admirable', 'get_foundedtime', 'get_category', 'get_customleader', 'get_flag', 'get_currency', 'get_endorsements', 'get_lastlogin', 'get_region', 'get_religion', 'get_capital', 'get_name', 'get_type', 'get_happenings', 'get_crime', 'get_govtdesc', 'get_majorindustry', 'get_influence', 'get_customcapital', 'get_tax', 'get_tgcanrecruit', 'get_demonym2', 'get_legislation', 'get_poorest', 'get_wa', 'get_sectors', 'get_deaths', 'get_dbid', 'get_policies', 'get_scvote', 'get_lastactivity', 'get_demonym', 'get_freedom', 'get_animal', 'get_factbooklist', 'get_industrydesc', 'get_income', 'get_population', 'get_founded', 'get_richest', 'get_demonym2plural', 'get_gdp', 'get_dispatches', 'get_publicsector', 'get_fullname', 'get_motto', 'get_tgcancampaign', 'get_govt', 'get_sensibilities', 'get_dispatchlist', 'get_wabadges', 'get_factbooks', 'get_animaltrait', 'get_leader'}



Is pynationstates thread safe?
==============================

pynationstates does have locks in place that should make it safe to use across threads. However, the author recommends you have independent `nationstates.Nationstates` instances for earch thread. This guarntees everything will work.
Pynationstates is now fully thread safe and will work as is across threads.
2 changes: 1 addition & 1 deletion docs/pages/ratelimit.rst
Expand Up @@ -8,7 +8,7 @@ The Nationstates package constains an automatic ratelimiter. It's a simple imple
How to use the Rate Limiter
---------------------------

The Rate Limit system is completely automatic, and will simply ``time.sleep()`` your thread if it thinks your getting close to the api limit. Each thread keeps tracks of its own requests, so it can leave this period as soon as possible. Due to the possibility of multiple threads, this library slightly lowers the amount of requests per 30 second block to prevent accidental breaches of the ratelimit.
The Rate Limit system is completely automatic, and will simply ``time.sleep()`` your thread if it thinks your getting close to the api limit. Each process keeps tracks of its own requests, so it can leave this period as soon as possible. Due to the possibility of multiple processes, this library slightly lowers the amount of requests per 30 second block to prevent accidental breaches of the ratelimit.

Disabling the Rate Limiter
--------------------------
Expand Down
13 changes: 10 additions & 3 deletions nationstates/exceptions.py
Expand Up @@ -8,14 +8,21 @@
NotFound,
APIRateLimitBan,
APIUsageError,
DispatchTooRecent,
ActionTooRecent,
InternalServerError,
CloudflareServerError,
BadRequest,
ServerError,
BadResponse
BadResponse,
BetaDisabled
)

class NotAuthenticated(Exception):
"""If an action requires Authentication but the object isn't Authenticated"""
pass
pass

class BetaDisabled(Exception):
"""Beta Disabled"""
message = 'This Feature is marked as Beta. Pass True to Nationstates() object to enable this functionality'

DispatchTooRecent = ActionTooRecent
2 changes: 2 additions & 0 deletions nationstates/info.py
Expand Up @@ -149,5 +149,7 @@
)

dispatch_to_soon = 'Your nation is attempting to issue many announcements in a short period of time. Please wait for the international press to catch their breath, then try again.'
rmb_to_soon = 'To prevent spam, you cannot post a message so soon after your previous one. Please try again in a few moments.'



7 changes: 4 additions & 3 deletions nationstates/main.py
Expand Up @@ -6,20 +6,21 @@ class Nationstates:
def __init__(self, user_agent, version="11", ratelimit_sleep=True,
ratelimit_limit=40, ratelimit_timeframe=30, ratelimit_sleep_time=4,
ratelimit_maxsleeps=10, ratelimit_enabled=True, do_retry=True,
retry_sleep=5, max_retries=5, use_nsdict=True, use_session=True, threading_mode=False,
max_ongoing_requests=20):
retry_sleep=5, max_retries=5, use_nsdict=True, use_session=True, threading_mode=True,
max_ongoing_requests=20, enable_beta=False):
self.api = nsapiwrapper.Api(user_agent, version=version,
ratelimit_sleep=ratelimit_sleep,
ratelimit_sleep_time=ratelimit_sleep_time,
ratelimit_max=ratelimit_limit,
ratelimit_within=ratelimit_timeframe,
ratelimit_maxsleeps=ratelimit_maxsleeps,
ratelimit_enabled=ratelimit_enabled,
use_session=use_session and threading_mode)
use_session=use_session and not threading_mode)
self.do_retry = do_retry
self.retry_sleep = retry_sleep
self.max_retries = max_retries
self.use_nsdict = use_nsdict
self.enable_beta = enable_beta

def nation(self, nation_name, password=None, autologin=None):
"""Setup access to the Nation API with the Nation object
Expand Down
8 changes: 6 additions & 2 deletions nationstates/nsapiwrapper/exceptions.py
Expand Up @@ -36,7 +36,8 @@ class APIRateLimitBan(APIError):
class APIUsageError(APIError):
pass

class DispatchTooRecent(APIUsageError):

class ActionTooRecent(APIUsageError):
pass

class ServerError(APIError):
Expand All @@ -46,6 +47,7 @@ class ServerError(APIError):
class ConflictError(ServerError):
"""ConflictError from Server"""
pass

class InternalServerError(ServerError):
pass

Expand All @@ -56,6 +58,8 @@ class BadResponse(ServerError):
# When NS returns an odd response we can't otherwise classify
pass


class BadRequest(APIError):
pass

class BetaDisabled(NSBaseError):
pass
113 changes: 71 additions & 42 deletions nationstates/objects.py
Expand Up @@ -6,9 +6,9 @@
from time import sleep
from functools import wraps

from .exceptions import ConflictError, InternalServerError, CloudflareServerError, APIUsageError, NotAuthenticated, BadResponse, DispatchTooRecent
from .exceptions import ConflictError, InternalServerError, CloudflareServerError, APIUsageError, NotAuthenticated, BadResponse, DispatchTooRecent, BetaDisabled, ActionTooRecent, NotFound
from requests.exceptions import ConnectionError
from .info import nation_shards, region_shards, world_shards, wa_shards, individual_cards_shards, dispatch_to_soon
from .info import nation_shards, region_shards, world_shards, wa_shards, individual_cards_shards, dispatch_to_soon, rmb_to_soon
import html


Expand Down Expand Up @@ -44,17 +44,30 @@ def dispatch_token(resp, use_exception):
return False
return data['success']

def dispatch_error_check(resp, use_exception):
def dispatch_error_check(resp, use_exception, custom_message='PlaceHolder'):
if custom_message:
checks = {dispatch_to_soon, rmb_to_soon, custom_message}
else:
checks = {dispatch_to_soon, rmb_to_soon}

data = resp['data'][Nation.api_name]
if data.get('error'):
if use_exception:
if data['error'] == dispatch_to_soon:
raise DispatchTooRecent(data['error'])
if data['error'] in checks:
raise ActionTooRecent(data['error'])
raise APIUsageError(data['error'])
else:
return False
return True

def action_full_response(api, final_resp, full_response):
if final_resp is False:
return False
elif full_response:
return final_resp
else:
return final_resp['data'][api.api_name]

class NSDict(dict):
"""Specialized Dict"""

Expand Down Expand Up @@ -219,6 +232,10 @@ def __get_shards__(self, *args, full_response=False, use_post=False):
resp = self.request(shards=args, full_response=full_response, use_post=False)
return resp

def _check_beta(self):
if not self.api_mother.enable_beta:
raise BetaDisabled('Beta Endpoints are not enabled. Pass enable_beta = True to nationstates.Nationstates to suppress')

def get_shards(self, *args, full_response=False):
"""Get Shards"""
return self.__get_shards__(*args, full_response=full_response, use_post=False)
Expand Down Expand Up @@ -268,6 +285,13 @@ def _check_auth(self):
if not self.is_auth:
raise NotAuthenticated("Action requires authentication")

def exists(self):
try:
self.get_shards('name')
return True
except NotFound:
return False

def authenticate(self, password=None, autologin=None):
self._set_apiwrapper(self._determine_api(self.nation_name, password, autologin))
return self
Expand All @@ -287,71 +311,65 @@ def pick_issue(self, issue_id, option, full_response=False, raise_exception_if_f
else:
return resp["data"][self.api_name]

def _dispatch_request(self, dispatch, use_exception=True, **kwargs):
self._check_auth()
token_resp = self.command('dispatch', dispatch=dispatch, mode='prepare', nation=self.nation_name, full_response=True, use_post=True, **kwargs)

def _prepare_execute_request(self, name, bad_message, use_exception=True, **kwargs):
token_resp = self.command(name, mode='prepare', nation=self.nation_name, full_response=True, use_post=True, **kwargs)
token = dispatch_token(token_resp, use_exception)
if use_exception is False and token is False:
return False
final_resp = self.command('dispatch', dispatch=dispatch, mode='execute', token=token, nation=self.nation_name, full_response=True, use_post=True, **kwargs)
check = dispatch_error_check(final_resp, use_exception)
# Check was False - we need to return False down the line
final_resp = self.command(name, mode='execute', nation=self.nation_name, token=token, full_response=True, use_post=True, **kwargs)
check = dispatch_error_check(final_resp, use_exception, bad_message)
if not check:
return check
else:
return final_resp

def _dispatch(self, dispatch, use_exception=True, **kwargs):
limit = 10
sleep_time = 5
def execute_command(self, name, use_exception=True, limit=10, sleep_time=5, too_soon_message=None, **kwargs):
""" Executes a command that follows the prepare and execute paradigm """
last_exc = None
while limit > 0:
try:
return self._dispatch_request(dispatch, use_exception=use_exception, **kwargs)
except DispatchTooRecent as Exc:
return self._prepare_execute_request(name, too_soon_message, use_exception=use_exception, **kwargs)
except ActionTooRecent as Exc:
limit = limit - 1
sleep_thread(sleep_time)
last_exc = Exc
sleep_thread(sleep_time)
raise last_exc

def _dispatch(self, dispatch, use_exception=True, **kwargs):
if not self.api_mother.enable_beta:
print('Warning: Beta Features are not enabled, but due to backwards compatiblity this method is not disabled. Enable Beta Flag to supress this message')
self._check_auth()


kwargs['dispatch'] = dispatch

return self.execute_command('dispatch', use_exception=use_exception, **kwargs)

def create_dispatch(self, title=None, text=None, category=None, subcategory=None, full_response=False, use_exception=True):
cant_be_none(title=title, text=text, category=category, subcategory=subcategory)

final_resp = self._dispatch('add', title=title, text=text,
category=category, subcategory=subcategory, use_exception=use_exception)

if final_resp is False:
return False
elif full_response:
return final_resp
else:
return final_resp['data'][self.api_name]
return action_full_response(self, final_resp, full_response)

def edit_dispatch(self, dispatch_id=None, title=None, text=None, category=None, subcategory=None, full_response=False, use_exception=True):
cant_be_none(dispatch_id=dispatch_id, title=title, text=text, category=category, subcategory=subcategory)

final_resp = self._dispatch('edit', dispatchid=dispatch_id, title=title, text=text,
category=category, subcategory=subcategory, use_exception=use_exception)

if final_resp is False:
return False
elif full_response:
return final_resp
else:
return final_resp['data'][self.api_name]
return action_full_response(self, final_resp, full_response)


def remove_dispatch(self, dispatch_id=None, full_response=False, use_exception=True):
cant_be_none(dispatch_id=dispatch_id)

final_resp = self._dispatch('remove', dispatchid=dispatch_id, use_exception=use_exception)

if final_resp is False:
return False
elif full_response:
return final_resp
else:
return final_resp['data'][self.api_name]
return action_full_response(self, final_resp, full_response)


def send_telegram(self, telegram=None, client_key=None, tgid=None, key=None):
"""Sends Telegram. Can either provide a telegram directly, or provide the api details and created internally
Expand All @@ -367,6 +385,15 @@ def send_telegram(self, telegram=None, client_key=None, tgid=None, key=None):
telegram = self.api_mother.telegram(client_key, tgid, key)
telegram.send_telegram(self.nation_name)

def send_rmb(self, region=None, text=None, full_response=False):
self._check_beta()
self._check_auth()
cant_be_none(region=region, text=text)
if isinstance(region, Region):
region = region.region_name
final_resp = self.execute_command('rmbpost', nation_name=self.nation_name, region=region, text=text)
return action_full_response(self, final_resp, full_response)

def verify(self, checksum=None, token=None):
"""Wraps around the verify API"""
cant_be_none(checksum=checksum, token=token)
Expand Down Expand Up @@ -401,6 +428,13 @@ def __repr__(self):
value=self.region_name,
hexloc=hex(id(self)).upper().replace("X", "x"))

def exists(self):
try:
self.get_shards('name')
return True
except NotFound:
return False

@property
def nations(self):
resp = self._auto_shard("nations")
Expand Down Expand Up @@ -450,13 +484,8 @@ def _determine_api(self, chamber):

@property
def nations(self):
resp = self._auto_shard("nations")
return tuple(self.api_mother.nation(x) for x in resp.split(":"))

@property
def regions(self):
resp = self._auto_shard("regions")
return tuple(self.api_mother.region(x) for x in resp.split(":"))
resp = self._auto_shard("members")
return tuple(self.api_mother.nation(x) for x in resp.split(","))

class Telegram(API_WRAPPER):
api_name = TelegramAPI.api_name
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -26,7 +26,7 @@
"""


version = '3.0.3.9'
version = '3.1.4'

from setuptools import setup
setup(name='nationstates',
Expand Down

0 comments on commit d263b92

Please sign in to comment.