Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.1.4 New Version Release #20

Merged
merged 7 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"""


version = '3.0.3.9'
version = '3.1.4'

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