NCBI C++ ToolKit
country_conflict.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: country_conflict.cpp 47479 2023-05-02 13:24:02Z ucko $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Igor Filippov
27  */
28 #include <ncbi_pch.hpp>
30 #include <objmgr/feat_ci.hpp>
31 #include <objmgr/bioseq_ci.hpp>
42 #include <wx/msgdlg.h>
43 
45 
48 
49 
50 
51 IMPLEMENT_DYNAMIC_CLASS( CCountryConflict, wxDialog )
52 
53 BEGIN_EVENT_TABLE( CCountryConflict, wxDialog )
54 
55  EVT_BUTTON( wxID_OK, CCountryConflict::OnClickOk )
56 
57  EVT_BUTTON( wxID_CANCEL, CCountryConflict::OnClickCancel )
58 
59 
61 
63 {
64  Init();
65 }
66 
68  wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style ) : m_TopSeqEntry(seh)
69 {
71  for ( ; b_iter ; ++b_iter )
72  {
73  m_Found = false;
74  x_FindBioSource(b_iter->GetSeq_entry_Handle());
75  if (!m_Found && b_iter->GetSeq_entry_Handle().HasParentEntry())
76  x_FindBioSource(b_iter->GetSeq_entry_Handle().GetParentEntry());
77  }
78 
79  Init();
80  Create(parent, id, caption, pos, size, style);
81 }
82 
83 bool CCountryConflict::Create( wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style )
84 {
85  SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
86  wxDialog::Create( parent, id, caption, pos, size, style );
87 
89  if (GetSizer())
90  {
91  GetSizer()->SetSizeHints(this);
92  }
93  Centre();
94  return true;
95 }
96 
98 {
99 }
100 
101 
102 /*!
103  * Member initialisation
104  */
105 
107 {
108  m_Grid=NULL;
109 }
110 
111 
113 {
114  CCountryConflict* itemDialog1 = this;
115 
116  wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
117  itemDialog1->SetSizer(itemBoxSizer2);
118 
119  wxBoxSizer* itemBoxSizer3 = new wxBoxSizer(wxHORIZONTAL);
120  itemBoxSizer2->Add(itemBoxSizer3, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
121 
122  wxArrayString itemChoiceStrings, itemChoiceStringsWritable;
123 
124  if (m_LatLon.empty())
125  {
126  wxMessageBox(wxT("No LatLon records found"), wxT("Error"), wxOK | wxICON_ERROR);
127  NCBI_THROW( CException, eUnknown, "No LatLon records found" );
128  }
129 
131  if (values_table->GetNum_rows() < 1)
132  {
133  wxMessageBox(wxT("No LatLon records found or all records correctly formatted"), wxT("Error"), wxOK | wxICON_ERROR);
134  NCBI_THROW( CException, eUnknown, "No LatLon records found or all records correctly formatted" );
135  }
136  CRef<CSeq_table> choices = GetChoices(values_table);
137  int glyph_col = GetCollapsible();
138  m_GridPanel = new CSeqTableGridPanel(this, values_table, choices, glyph_col);
139  itemBoxSizer3->Add(m_GridPanel, 0, wxALIGN_TOP|wxALL, 5);
141  CSeqTableGrid *gridAdapter = new CSeqTableGrid(values_table);
142  m_Grid->SetTable(gridAdapter, true);
143  m_Grid->AutoSizeColumns();
144  int l_height = m_Grid->GetColLabelSize();
145  m_Grid->SetColLabelSize( 2 * l_height );
146 
147  int pos = 0;
148  ITERATE (CSeq_table::TColumns, it, values_table->GetColumns())
149  {
150  if (pos > 0)
151  {
152  if ((*it)->IsSetHeader() && (*it)->GetHeader().IsSetTitle() )
153  {
154  string title = (*it)->GetHeader().GetTitle();
155  if (!title.empty())
156  {
157  itemChoiceStrings.Add(wxString(title));
158  if (!IsReadOnlyColumn(title))
159  itemChoiceStringsWritable.Add(wxString(title));
160  }
161  if (IsReadOnlyColumn(title))
162  m_GridPanel->MakeColumnReadOnly(pos - 1, true);
163  }
164  }
165  pos++;
166  }
167 
168  if (glyph_col >= 0 && glyph_col+1 < m_Grid->GetNumberCols())
169  {
170  m_GridPanel->InitColumnCollapse(glyph_col+1);
171  }
172 
173 
174  wxBoxSizer* itemBoxSizer4 = new wxBoxSizer(wxHORIZONTAL);
175  itemBoxSizer2->Add(itemBoxSizer4, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
176 
177  m_StringConstraintPanel = new CStringConstraintSelect( itemDialog1, m_GridPanel, itemChoiceStrings, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
178  itemBoxSizer4->Add(m_StringConstraintPanel, 0, wxALIGN_TOP|wxALL|wxFIXED_MINSIZE, 0);
179 
180  wxBoxSizer* itemBoxSizer13 = new wxBoxSizer(wxHORIZONTAL);
181  itemBoxSizer2->Add(itemBoxSizer13, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
182 
183  wxButton* itemButton14 = new wxButton( itemDialog1, wxID_OK, _("Accept"), wxDefaultPosition, wxDefaultSize, 0 );
184  itemBoxSizer13->Add(itemButton14, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
185 
186  wxButton* itemButton15 = new wxButton( itemDialog1, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
187  itemBoxSizer13->Add(itemButton15, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
188 }
189 
191 {
192 
195  id_col->SetHeader().SetTitle(kSequenceIdColLabel);
196 
197  CRef<CSeqTable_column> expand_col(new CSeqTable_column());
198  expand_col->SetHeader().SetTitle("");
199  expand_col->SetHeader().SetField_name("expand");
200  expand_col->SetData().SetString();
201 
202  CRef<CSeqTable_column> current_col(new CSeqTable_column());
203  current_col->SetHeader().SetTitle("Lat-Lon");
204  current_col->SetHeader().SetField_name("current");
205  current_col->SetData().SetString();
206 
207  CRef<CSeqTable_column> country_col(new CSeqTable_column());
208  country_col->SetHeader().SetTitle("Country");
209  country_col->SetHeader().SetField_name("country");
210  country_col->SetData().SetString();
211 
212  CRef<CSeqTable_column> suggested_col(new CSeqTable_column());
213  suggested_col->SetHeader().SetTitle("Suggested Correction");
214  suggested_col->SetHeader().SetField_name("suggested");
215  suggested_col->SetData().SetString();
216 
217 
218  // bogus column to include last, otherwise deletion of the previous column will not work
219  CRef<CSeqTable_column> bogus_col(new CSeqTable_column());
220  bogus_col->SetHeader().SetTitle("");
221  bogus_col->SetHeader().SetField_name("");
222  bogus_col->SetData().SetString();
223 
225  table->SetColumns().push_back(id_col);
226  table->SetColumns().push_back(expand_col);
227  table->SetColumns().push_back(current_col);
228  table->SetColumns().push_back(country_col);
229  table->SetColumns().push_back(suggested_col);
230  table->SetColumns().push_back(bogus_col);
231 
232  size_t row = 0;
233  m_ReplaceLatLon.clear();
234  for ( vector<string>::iterator fi = m_LatLon.begin(); fi != m_LatLon.end() ; ++fi )
235  {
236  string country = m_Country[row];
237  string latlon = *fi;
239  string msg = CSubSource::ValidateLatLonCountry (country, latlon, false, errcode);
240  if (errcode == CSubSource::eLatLonCountryErr_None)
241  continue;
242  CRef<CSeq_id> id(new CSeq_id());
243  id->SetLocal().SetId(static_cast<CObject_id::TId>(row));
244  id_col->SetData().SetId().push_back(id);
245  expand_col->SetData().SetString().push_back("");
246  current_col->SetData().SetString().push_back(*fi);
247  country_col->SetData().SetString().push_back(country);
248  suggested_col->SetData().SetString().push_back(msg);
249  bogus_col->SetData().SetString().push_back("");
250  m_ReplaceLatLon.push_back(latlon);
251  row++;
252  }
253 
254  table->SetNum_rows(static_cast<CSeq_table::TNum_rows>(row));
255 
256  return table;
257 }
258 
259 
261 {
262  bool select_all = false;
263  if (!m_Grid->IsSelection() && values_table->GetNum_rows() > 0)
264  {
265  if (wxMessageBox(ToWxString("Apply to all?"), wxT("Nothing is selected"), wxOK | wxCANCEL, NULL) == wxOK)
266  {
267  select_all = true;
268  }
269  }
270 
272  CRef<CCmdComposite> cmd(new CCmdComposite("Country Conflict Tool"));
273  for (size_t row = 0; row < values_table->GetColumn("current").GetData().GetString().size(); ++row)
274  {
275  string old_latlon = values_table->GetColumn("current").GetData().GetString()[row];
276  string old_country = m_Country[row];
277  string new_latlon = m_ReplaceLatLon[row];
278  string new_country = values_table->GetColumn("country").GetData().GetString()[row];
279  if ((old_latlon != new_latlon || old_country != new_country)
280  && (m_Grid->IsInSelection(static_cast<int>(row),0) || select_all))
281  m_LatLonCountry[make_pair(old_latlon, old_country)] = make_pair(new_latlon, new_country);
282  }
283 
284 
286  for ( ; b_iter ; ++b_iter )
287  {
288  m_Found = false;
290  if (!m_Found && b_iter->GetSeq_entry_Handle().HasParentEntry())
292  }
293 
294  return cmd;
295 }
296 
298 {
299  CRef<CSeq_table> values_table = m_GridPanel->GetValuesTable();
301  return cmd;
302 }
303 
305 {
306  return "Invalid operation in Country Conflict Tool";
307 }
308 
309 
310 /*!
311  * Should we show tooltips?
312  */
313 
315 {
316  return true;
317 }
318 
319 /*!
320  * Get bitmap resources
321  */
322 
323 wxBitmap CCountryConflict::GetBitmapResource( const wxString& name )
324 {
325  // Bitmap retrieval
326 ////@begin CCountryConflict bitmap retrieval
327  wxUnusedVar(name);
328  return wxNullBitmap;
329 ////@end CCountryConflict bitmap retrieval
330 }
331 
332 /*!
333  * Get icon resources
334  */
335 
336 wxIcon CCountryConflict::GetIconResource( const wxString& name )
337 {
338  // Icon retrieval
339 ////@begin CCountryConflict icon retrieval
340  wxUnusedVar(name);
341  return wxNullIcon;
342 ////@end CCountryConflict icon retrieval
343 }
344 
345 /*!
346  * wxEVT_COMMAND_BUTTON_CLICKED event handler for ID_BUTTON
347  */
348 
349 void CCountryConflict::OnClickOk( wxCommandEvent& event )
350 {
351 ////@begin wxEVT_COMMAND_BUTTON_CLICKED event handler for ID_BUTTON in CCountryConflict.
352  // Before editing this code, remove the block markers.
353  event.Skip();
354 ////@end wxEVT_COMMAND_BUTTON_CLICKED event handler for ID_BUTTON in CCountryConflict.
355 }
356 
357 /*!
358  * wxEVT_COMMAND_BUTTON_CLICKED event handler for ID_BUTTON1
359  */
360 
361 void CCountryConflict::OnClickCancel( wxCommandEvent& event )
362 {
363  bool modified = m_GridPanel->GetModified();
364  if (modified)
365  {
366  wxMessageDialog dlg(this,_("Discard modifications?"), _("Attention"),wxOK|wxCANCEL|wxCENTRE);
367  if (dlg.ShowModal() == wxID_OK)
368  {
369  event.Skip();
370  }
371  }
372  else
373  event.Skip();
374 }
375 
377 {
378  string latlon;
379  FOR_EACH_SUBSOURCE_ON_BIOSOURCE(subsource, biosource)
380  {
381  if ((*subsource)->IsSetSubtype() &&
382  (*subsource)->GetSubtype() == CSubSource::eSubtype_lat_lon
383  && (*subsource)->IsSetName())
384  {
385  latlon = (*subsource)->GetName();
386  break;
387  }
388  }
389 
390  string country;
391  FOR_EACH_SUBSOURCE_ON_BIOSOURCE(subsource, biosource)
392  {
393  if ((*subsource)->IsSetSubtype() &&
394  (*subsource)->GetSubtype() == CSubSource::eSubtype_country
395  && (*subsource)->IsSetName())
396  {
397  country = (*subsource)->GetName();
398  break;
399  }
400  }
401  if (!latlon.empty() && !country.empty())
402  {
403  m_LatLon.push_back(latlon);
404  m_Country.push_back(country);
405  m_Found = true;
406  }
407 }
408 
410 {
411  bool modified = false;
412 
413  string latlon;
414  FOR_EACH_SUBSOURCE_ON_BIOSOURCE(subsource,biosource)
415  {
416  if ((*subsource)->IsSetSubtype() &&
417  (*subsource)->GetSubtype() == CSubSource::eSubtype_lat_lon &&
418  (*subsource)->IsSetName())
419  {
420  latlon = (*subsource)->GetName();
421  break;
422  }
423  }
424 
425  string country;
426  FOR_EACH_SUBSOURCE_ON_BIOSOURCE(subsource,biosource)
427  {
428  if ((*subsource)->IsSetSubtype() &&
429  (*subsource)->GetSubtype() == CSubSource::eSubtype_country &&
430  (*subsource)->IsSetName())
431  {
432  country = (*subsource)->GetName();
433  break;
434 
435  }
436  }
437  if (latlon.empty() || country.empty())
438  {
439  return modified;
440  }
441 
442  m_Found = true;
443  auto it = m_LatLonCountry.find(make_pair(latlon, country));
444  if (it != m_LatLonCountry.end()) {
445  string new_latlon = it->second.first;
446  string new_country = it->second.second;
447 
448  EDIT_EACH_SUBSOURCE_ON_BIOSOURCE(subsource, biosource)
449  {
450  if ((*subsource)->IsSetSubtype() &&
451  (*subsource)->GetSubtype() == CSubSource::eSubtype_lat_lon &&
452  (*subsource)->IsSetName())
453  {
454  (*subsource)->SetName(new_latlon);
455  modified = true;
456  break;
457  }
458  }
459  EDIT_EACH_SUBSOURCE_ON_BIOSOURCE(subsource, biosource)
460  {
461  if ((*subsource)->IsSetSubtype() &&
462  (*subsource)->GetSubtype() == CSubSource::eSubtype_country
463  && (*subsource)->IsSetName())
464  {
465  (*subsource)->SetName(new_country);
466  modified = true;
467  break;
468  }
469  }
470  }
471  return modified;
472 }
473 
475 {
476  if (!composite) {
478  if ((*it)->IsSource()) {
479  x_GatherLatLon((*it)->GetSource());
480  }
481  }
482  }
483  else {
485  if ((*it)->IsSource()) {
486  const CSeqdesc& orig_desc = **it;
487  CRef<CSeqdesc> new_desc(new CSeqdesc);
488  new_desc->Assign(orig_desc);
489  if (x_ApplyToBioSource(new_desc->SetSource())) {
491  CCmdChangeSeqdesc(m_TopSeqEntry.GetScope().GetSeq_entryHandle(se), orig_desc, *new_desc));
492  composite->AddCommand(*cmd);
493  }
494  }
495  }
496  }
497 
498  if (se.IsSet()) {
500  x_ApplyToDescriptors(**it, composite);
501  }
502  }
503 }
504 
506 {
507  x_ApplyToDescriptors(*(tse.GetCompleteSeq_entry()), composite);
508 
509  if (!composite) {
510  for (CFeat_CI feat_it(tse, SAnnotSelector(CSeqFeatData::e_Biosrc)); feat_it; ++feat_it) {
511  x_GatherLatLon(feat_it->GetData().GetBiosrc());
512  }
513  }
514  else {
515  for (CFeat_CI feat_it(tse, SAnnotSelector(CSeqFeatData::e_Biosrc)); feat_it; ++feat_it) {
516  CRef<CSeq_feat> new_feat(new CSeq_feat());
517  new_feat->Assign(feat_it->GetOriginalFeature());
518  if (x_ApplyToBioSource(new_feat->SetData().SetBiosrc())) {
519  CRef<CCmdChangeSeq_feat> cmd(new CCmdChangeSeq_feat(*feat_it, *new_feat));
520  composite->AddCommand(*cmd);
521  }
522  }
523  }
524 }
525 
526 
CBioseq_CI –.
Definition: bioseq_ci.hpp:69
void AddCommand(IEditCommand &command)
static bool ShowToolTips()
Should we show tooltips?
CStringConstraintSelect * m_StringConstraintPanel
void x_ApplyToDescriptors(const objects::CSeq_entry &se, CCmdComposite *composite)
CRef< CCmdComposite > GetCommand()
map< pair< string, string >, pair< string, string > > m_LatLonCountry
CRef< CCmdComposite > GetCommandFromValuesTable(CRef< objects::CSeq_table >)
vector< string > m_Country
wxBitmap GetBitmapResource(const wxString &name)
Retrieves bitmap resources.
void x_GatherLatLon(const objects::CBioSource &biosource)
wxIcon GetIconResource(const wxString &name)
Retrieves icon resources.
objects::CSeq_entry_Handle m_TopSeqEntry
vector< string > m_ReplaceLatLon
CSeqTableGridPanel * m_GridPanel
vector< string > m_LatLon
void x_FindBioSource(objects::CSeq_entry_Handle tse, CCmdComposite *composite=NULL)
~CCountryConflict()
Destructor.
CRef< objects::CSeq_table > GetChoices(CRef< objects::CSeq_table > values_table)
void OnClickOk(wxCommandEvent &event)
void OnClickCancel(wxCommandEvent &event)
bool x_ApplyToBioSource(objects::CBioSource &biosource)
void Init()
Initialises member variables.
bool IsReadOnlyColumn(string column_name)
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Country Conflict Tool"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU|wxCLOSE_BOX|wxTAB_TRAVERSAL)
Creation.
void CreateControls()
Creates the controls and sizers.
CRef< objects::CSeq_table > GetValuesTableFromSeqEntry()
CCountryConflict()
Constructors.
CFeat_CI –.
Definition: feat_ci.hpp:64
void MakeColumnReadOnly(int pos, bool val=true)
wxGrid * GetGrid(void)
void InitColumnCollapse(int col)
CRef< objects::CSeq_table > GetValuesTable()
CSeq_entry_Handle –.
Definition: Seq_entry.hpp:56
namespace ncbi::objects::
Definition: Seq_feat.hpp:58
const CSeqTable_column & GetColumn(CTempString column_name) const
Definition: Seq_table.cpp:65
@ eLatLonCountryErr_None
Definition: SubSource.hpp:190
static string ValidateLatLonCountry(const string &countryname, string &lat_lon, bool check_state, ELatLonCountryErr &errcode)
Definition: SubSource.cpp:2101
const_iterator end() const
Definition: map.hpp:152
void clear()
Definition: map.hpp:169
const_iterator find(const key_type &key) const
Definition: map.hpp:153
USING_SCOPE(ncbi::objects)
#define _(proto)
Definition: ct_nlmzip_i.h:78
#define wxFIXED_MINSIZE
const char * kSequenceIdColLabel
static CS_COMMAND * cmd
Definition: ct_dynamic.c:26
static void Init(void)
Definition: cursor6.c:76
#define ITERATE(Type, Var, Cont)
ITERATE macro to sequence through container elements.
Definition: ncbimisc.hpp:815
#define NULL
Definition: ncbistd.hpp:225
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string.
Definition: ncbiexpt.hpp:704
virtual void Assign(const CSerialObject &source, ESerialRecursionMode how=eRecursive)
Set object to copy of another one.
CSeq_entry_Handle GetSeq_entry_Handle(void) const
Get parent Seq-entry handle.
CConstRef< CSeq_entry > GetCompleteSeq_entry(void) const
Complete and get const reference to the seq-entry.
bool HasParentEntry(void) const
Check if current seq-entry has a parent.
CSeq_entry_Handle GetParentEntry(void) const
Get parent Seq-entry handle.
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
@ eSubtype_lat_lon
+/- decimal degrees
Definition: SubSource_.hpp:113
const TColumns & GetColumns(void) const
Get the Columns member data.
Definition: Seq_table_.hpp:433
void SetHeader(THeader &value)
Assign a value to Header data member.
vector< CRef< CSeqTable_column > > TColumns
Definition: Seq_table_.hpp:92
void SetData(TData &value)
Assign a value to Data data member.
TNum_rows GetNum_rows(void) const
Get the Num_rows member data.
Definition: Seq_table_.hpp:393
const TString & GetString(void) const
Get the variant data.
const TData & GetData(void) const
Get the Data member data.
void SetData(TData &value)
Assign a value to Data data member.
Definition: Seq_feat_.cpp:94
const TSet & GetSet(void) const
Get the variant data.
Definition: Seq_entry_.cpp:124
bool IsSet(void) const
Check if variant Set is selected.
Definition: Seq_entry_.hpp:263
TSource & SetSource(void)
Select the variant.
Definition: Seqdesc_.cpp:572
@ eMol_na
just a nucleic acid
Definition: Seq_inst_.hpp:113
<!DOCTYPE HTML >< html > n< header > n< title > PubSeq Gateway Help Page</title > n< style > n table
END_EVENT_TABLE()
#define wxT(x)
Definition: muParser.cpp:41
const struct ncbi::grid::netcache::search::fields::SIZE size
#define fi
Utility macros and typedefs for exploring NCBI objects from seqfeat.asn.
#define FOR_EACH_SUBSOURCE_ON_BIOSOURCE(Itr, Var)
FOR_EACH_SUBSOURCE_ON_BIOSOURCE EDIT_EACH_SUBSOURCE_ON_BIOSOURCE.
#define EDIT_EACH_SUBSOURCE_ON_BIOSOURCE(Itr, Var)
static static static wxID_ANY
#define FOR_EACH_SEQENTRY_ON_SEQSET(Itr, Var)
FOR_EACH_SEQENTRY_ON_SEQSET EDIT_EACH_SEQENTRY_ON_SEQSET.
#define FOR_EACH_SEQDESC_ON_SEQENTRY(Itr, Var)
FOR_EACH_SEQDESC_ON_SEQENTRY EDIT_EACH_SEQDESC_ON_SEQENTRY.
#define row(bind, expected)
Definition: string_bind.c:73
SAnnotSelector –.
wxString ToWxString(const string &s)
Definition: wx_utils.hpp:173
Modified on Sun Apr 14 05:28:34 2024 by modify_doxy.py rev. 669887